howamiprompting 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/util.js ADDED
@@ -0,0 +1,89 @@
1
+ import fs from 'node:fs';
2
+ import fsp from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ export const HOME = os.homedir();
8
+ export const PLATFORM = process.platform;
9
+
10
+ /** Per-OS application-support directory for VS Code–family apps. */
11
+ export function appSupport() {
12
+ if (PLATFORM === 'darwin') return path.join(HOME, 'Library', 'Application Support');
13
+ if (PLATFORM === 'win32') return process.env.APPDATA || path.join(HOME, 'AppData', 'Roaming');
14
+ return process.env.XDG_CONFIG_HOME || path.join(HOME, '.config');
15
+ }
16
+
17
+ export const exists = p => { try { fs.accessSync(p); return true; } catch { return false; } };
18
+ export const isDir = p => { try { return fs.statSync(p).isDirectory(); } catch { return false; } };
19
+
20
+ export async function readText(p, max = 200 * 1024 * 1024) {
21
+ try {
22
+ const st = await fsp.stat(p);
23
+ if (st.size > max) return null;
24
+ return await fsp.readFile(p, 'utf8');
25
+ } catch { return null; }
26
+ }
27
+
28
+ export async function readJson(p) {
29
+ const t = await readText(p);
30
+ if (t == null) return null;
31
+ try { return JSON.parse(t); } catch { return null; }
32
+ }
33
+
34
+ /** Iterate JSONL lines lazily-ish (file is read whole; sessions are rarely huge). */
35
+ export async function* jsonl(p) {
36
+ const t = await readText(p);
37
+ if (!t) return;
38
+ for (const line of t.split(/\r?\n/)) {
39
+ if (!line || line[0] !== '{') continue;
40
+ try { yield JSON.parse(line); } catch { /* skip */ }
41
+ }
42
+ }
43
+
44
+ /** Recursive file finder with depth + count limits. */
45
+ export async function find(root, test, { depth = 6, limit = 20000, skipDirs = SKIP } = {}) {
46
+ const out = [];
47
+ async function walk(dir, d) {
48
+ if (out.length >= limit || d > depth) return;
49
+ let ents;
50
+ try { ents = await fsp.readdir(dir, { withFileTypes: true }); } catch { return; }
51
+ for (const e of ents) {
52
+ if (out.length >= limit) return;
53
+ const p = path.join(dir, e.name);
54
+ if (e.isDirectory()) {
55
+ if (skipDirs.has(e.name)) continue;
56
+ await walk(p, d + 1);
57
+ } else if (e.isFile() && test(p, e.name)) out.push(p);
58
+ }
59
+ }
60
+ await walk(root, 0);
61
+ return out;
62
+ }
63
+ export const SKIP = new Set(['node_modules', '.git', 'Library', '.Trash', 'vendor', '.cache', 'dist', 'build', '.next', '.venv', 'venv', '__pycache__', 'Pods', 'DerivedData', '.npm', '.cargo', '.rustup', 'go', '.gradle', '.m2', 'Applications', 'Music', 'Movies', 'Pictures', 'Photos Library.photoslibrary', '.docker', 'AppData', 'snap']);
64
+
65
+ export function which(cmd) {
66
+ const r = spawnSync(PLATFORM === 'win32' ? 'where' : 'which', [cmd], { encoding: 'utf8' });
67
+ if (r.status !== 0) return null;
68
+ return (r.stdout || '').split(/\r?\n/)[0].trim() || null;
69
+ }
70
+
71
+ export function toISO(t) {
72
+ if (t == null || t === '') return null;
73
+ if (typeof t === 'number') return new Date(t < 1e12 ? t * 1000 : t).toISOString();
74
+ const d = new Date(t);
75
+ return isNaN(d) ? null : d.toISOString();
76
+ }
77
+
78
+ export function projectName(p) {
79
+ if (!p) return null;
80
+ return path.basename(String(p).replace(/[\\/]+$/, '')) || null;
81
+ }
82
+
83
+ export const sleep = ms => new Promise(r => setTimeout(r, ms));
84
+
85
+ export function dataDir() {
86
+ const d = process.env.HOWAMIPROMPTING_HOME || path.join(HOME, '.howamiprompting');
87
+ fs.mkdirSync(d, { recursive: true });
88
+ return d;
89
+ }
package/ui/app.css ADDED
@@ -0,0 +1,227 @@
1
+ :root {
2
+ --ink: #16142b; --cream: #fff7e8; --paper: #fff; --yellow: #ffd84d; --pink: #ff5c8a; --blue: #4b5cff;
3
+ --mint: #2ee6a6; --orange: #ff9a3d; --purple: #9b6bff; --muted: #6b6880; --soft-pink: #ffd2dc; --soft-mint: #e3fff4; --soft-blue: #e5e8ff;
4
+ --line: 3px solid var(--ink); --shadow: 6px 6px 0 var(--ink); --shadow-sm: 4px 4px 0 var(--ink); --r: 22px;
5
+ --font: 'Bricolage Grotesque', 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
6
+ --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
7
+ }
8
+ [hidden] { display: none !important; }
9
+ * { box-sizing: border-box; }
10
+ html { scroll-behavior: smooth; }
11
+ body { margin: 0; font-family: var(--font); color: var(--ink); background: var(--cream); -webkit-font-smoothing: antialiased; overflow-x: hidden; }
12
+ body::before { content: ''; position: fixed; inset: 0; z-index: -2; background-image: radial-gradient(rgba(22,20,43,.11) 1.4px, transparent 1.4px); background-size: 22px 22px; }
13
+ a { color: inherit; }
14
+ h1, h2, h3, h4 { margin: 0 0 .4em; letter-spacing: -.03em; line-height: 1.05; }
15
+ h2 { font-size: clamp(30px, 4vw, 44px); font-weight: 800; }
16
+ h3 { font-size: 21px; font-weight: 800; }
17
+ p { line-height: 1.55; }
18
+ .muted { color: var(--muted); }
19
+ code, .mono { font-family: var(--mono); }
20
+ .blob { position: fixed; z-index: -1; border-radius: 50%; opacity: .5; animation: float 14s ease-in-out infinite; pointer-events: none; }
21
+ .blob.b1 { width: 360px; height: 360px; background: var(--yellow); top: -110px; right: -90px; }
22
+ .blob.b2 { width: 260px; height: 260px; background: var(--pink); bottom: -90px; left: -80px; animation-delay: -5s; }
23
+ .blob.b3 { width: 180px; height: 180px; background: var(--mint); top: 48%; right: -70px; animation-delay: -9s; }
24
+ @keyframes float { 50% { transform: translate(-30px, 25px) scale(1.08); } }
25
+
26
+ /* nav */
27
+ .nav { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 14px 26px; background: rgba(255,247,232,.88); backdrop-filter: blur(10px); border-bottom: 3px solid var(--ink); }
28
+ .logo { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 19px; letter-spacing: -.02em; white-space: nowrap; }
29
+ .logo .q { width: 32px; height: 32px; border-radius: 50%; background: var(--yellow); border: var(--line); display: grid; place-items: center; box-shadow: 2px 2px 0 var(--ink); font-size: 15px; }
30
+ .logo em { font-style: normal; color: var(--pink); }
31
+ .tabs { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; }
32
+ .tabs button { font: inherit; font-weight: 800; font-size: 14px; border: 2px solid var(--ink); background: var(--paper); border-radius: 99px; padding: 7px 14px; cursor: pointer; transition: transform .12s; }
33
+ .tabs button:hover { transform: translateY(-2px); }
34
+ .tabs button.on { background: var(--ink); color: var(--yellow); }
35
+ .star { font-weight: 800; font-size: 14px; text-decoration: none; border: 2px solid var(--ink); background: var(--yellow); border-radius: 99px; padding: 7px 14px; box-shadow: 2px 2px 0 var(--ink); white-space: nowrap; }
36
+ @media (max-width: 900px) { .nav { flex-wrap: wrap; padding: 12px 16px; } .tabs { order: 3; width: 100%; justify-content: flex-start; overflow-x: auto; flex-wrap: nowrap; } }
37
+
38
+ main { max-width: 1080px; margin: 0 auto; padding: 26px 22px 90px; }
39
+ @media (max-width: 640px) { main { padding: 18px 16px 70px; } }
40
+ .view { display: none; }
41
+ .view.on { display: block; animation: rise .5s cubic-bezier(.2,1.3,.4,1) both; }
42
+ @keyframes rise { from { opacity: 0; transform: translateY(24px); } }
43
+ @keyframes pop { from { opacity: 0; transform: scale(.6) rotate(-6deg); } }
44
+
45
+ .card { background: var(--paper); border: var(--line); border-radius: var(--r); box-shadow: var(--shadow); padding: 26px; }
46
+ .card + .card, .section + .section { margin-top: 24px; }
47
+ .section { margin-top: 40px; }
48
+ .section-head { display: flex; align-items: baseline; gap: 12px; margin: 46px 0 14px; flex-wrap: wrap; }
49
+ .section-head h2 { margin: 0; }
50
+ .tag { display: inline-block; font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; background: var(--ink); color: var(--yellow); padding: 5px 10px; border-radius: 8px; }
51
+ .grid2 { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 22px; }
52
+ .grid3 { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 18px; }
53
+ .grid4 { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 14px; }
54
+ @media (max-width: 860px) { .grid3, .grid4 { grid-template-columns: 1fr 1fr; } .grid2 { grid-template-columns: 1fr; } }
55
+ @media (max-width: 480px) { .grid4, .grid3 { grid-template-columns: 1fr 1fr; gap: 10px; } }
56
+ .row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
57
+ .between { justify-content: space-between; }
58
+ .hl { background: linear-gradient(transparent 55%, var(--yellow) 55%); padding: 0 4px; }
59
+ .hl.pink { background: linear-gradient(transparent 55%, var(--pink) 55%); }
60
+ .hl.mint { background: linear-gradient(transparent 55%, var(--mint) 55%); }
61
+
62
+ .btn { font-family: inherit; font-weight: 800; font-size: 17px; border: var(--line); border-radius: 15px; padding: 12px 22px; cursor: pointer; background: var(--yellow); color: var(--ink); box-shadow: var(--shadow-sm); transition: transform .12s, box-shadow .12s; display: inline-flex; align-items: center; gap: 9px; text-decoration: none; }
63
+ .btn:hover { transform: translate(-2px,-2px); box-shadow: 6px 6px 0 var(--ink); }
64
+ .btn:active { transform: translate(3px,3px); box-shadow: 1px 1px 0 var(--ink); }
65
+ .btn.pink { background: var(--pink); color: #fff; } .btn.blue { background: var(--blue); color: #fff; } .btn.mint { background: var(--mint); } .btn.ghost { background: var(--paper); }
66
+ .btn.small { font-size: 14px; padding: 8px 14px; border-radius: 11px; } .btn.big { font-size: 21px; padding: 16px 30px; }
67
+ .btn:disabled { opacity: .45; cursor: not-allowed; transform: none; }
68
+ .pill { border: 2px solid var(--ink); border-radius: 99px; padding: 4px 12px; font-weight: 700; font-size: 13px; background: var(--paper); display: inline-flex; gap: 6px; align-items: center; }
69
+ .chip { border: 2px solid var(--ink); border-radius: 99px; padding: 6px 12px; font-weight: 700; background: var(--paper); color: var(--ink); font-size: 14px; display: inline-flex; gap: 6px; align-items: center; animation: pop .45s cubic-bezier(.2,1.6,.4,1) both; }
70
+ .chip b { background: var(--pink); color: #fff; border-radius: 99px; padding: 0 8px; }
71
+ .chips { display: flex; flex-wrap: wrap; gap: 9px; }
72
+ .copy { cursor: pointer; }
73
+
74
+ /* hero */
75
+ .hero { display: grid; grid-template-columns: 280px 1fr; gap: 34px; align-items: center; }
76
+ @media (max-width: 760px) { .hero { grid-template-columns: 1fr; text-align: center; } .hero .row { justify-content: center; } }
77
+ .dial { position: relative; width: 250px; height: 250px; margin: 0 auto; }
78
+ .dial svg { transform: rotate(-90deg); }
79
+ .dial .c { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; }
80
+ .dial .num { font-size: 84px; font-weight: 800; line-height: .9; }
81
+ .dial .of { font-weight: 800; color: var(--muted); font-size: 14px; letter-spacing: .06em; text-transform: uppercase; }
82
+ .level { display: inline-block; font-size: 22px; font-weight: 800; border: var(--line); border-radius: 13px; padding: 4px 14px; background: var(--yellow); box-shadow: var(--shadow-sm); transform: rotate(-3deg); }
83
+ .headline { font-size: clamp(28px, 3.6vw, 42px); font-weight: 800; letter-spacing: -.03em; line-height: 1.08; margin: 14px 0 10px; }
84
+ .delta { font-weight: 800; border-radius: 99px; padding: 3px 10px; border: 2px solid var(--ink); font-size: 14px; }
85
+ .delta.up { background: var(--mint); } .delta.down { background: var(--soft-pink); }
86
+
87
+ .persona { display: flex; gap: 18px; align-items: center; background: var(--ink); color: #fff; border-radius: var(--r); padding: 22px 24px; border: var(--line); box-shadow: var(--shadow); }
88
+ .persona .e { font-size: 58px; line-height: 1; animation: wobble 3s ease-in-out infinite; }
89
+ @keyframes wobble { 50% { transform: rotate(8deg) scale(1.06); } }
90
+ .persona h3 { color: var(--yellow); font-size: 26px; margin-bottom: 4px; }
91
+ .persona p { margin: 0; color: #dcd9f0; }
92
+
93
+ /* bars */
94
+ .bars { display: grid; gap: 15px; }
95
+ .bar .lab { display: flex; justify-content: space-between; font-weight: 800; margin-bottom: 6px; gap: 8px; }
96
+ .bar .lab small { font-weight: 600; color: var(--muted); }
97
+ .bar .track { height: 20px; border: var(--line); border-radius: 99px; background: var(--paper); overflow: hidden; }
98
+ .bar .fill { height: 100%; width: 0; border-right: 3px solid var(--ink); transition: width 1.4s cubic-bezier(.2,1,.3,1); }
99
+
100
+ .stat { border: var(--line); border-radius: 18px; padding: 16px; background: var(--paper); box-shadow: var(--shadow-sm); animation: pop .5s cubic-bezier(.2,1.6,.4,1) both; }
101
+ .stat .v { font-size: 34px; font-weight: 800; line-height: 1; }
102
+ .stat .k { font-weight: 600; color: var(--muted); font-size: 14px; margin-top: 6px; }
103
+
104
+ /* before/after */
105
+ .ba { border: var(--line); border-radius: 18px; background: var(--paper); overflow: hidden; margin-top: 14px; }
106
+ .ba .hd { display: flex; justify-content: space-between; gap: 10px; align-items: center; padding: 12px 16px; border-bottom: 2px dashed var(--ink); flex-wrap: wrap; }
107
+ .ba .hd h4 { margin: 0; font-size: 18px; }
108
+ .ba .body { display: grid; grid-template-columns: 1fr 36px 1fr; }
109
+ .ba .body > div { padding: 14px 16px; font-size: 15px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
110
+ .ba .was { background: #fff1f4; font-family: var(--mono); font-size: 13.5px; }
111
+ .ba .now { background: var(--soft-mint); font-weight: 600; }
112
+ .ba .arr { display: grid; place-items: center; font-weight: 800; font-size: 20px; padding: 0 !important; }
113
+ .ba .why { padding: 10px 16px; border-top: 2px solid #16142b14; font-size: 14px; color: var(--muted); font-weight: 600; }
114
+ .ba small.l { display: block; font-family: var(--font); font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 6px; color: var(--muted); }
115
+ @media (max-width: 640px) { .ba .body { grid-template-columns: 1fr; } .ba .arr { height: 28px; transform: rotate(90deg); } }
116
+ .sev { font-size: 11px; font-weight: 800; border: 2px solid var(--ink); border-radius: 99px; padding: 2px 9px; text-transform: uppercase; letter-spacing: .05em; }
117
+ .sev.high { background: var(--pink); color: #fff; } .sev.medium { background: var(--orange); } .sev.low { background: var(--yellow); }
118
+ .good { border: 2px solid var(--ink); border-radius: 14px; padding: 13px 15px; background: var(--soft-mint); margin-top: 12px; }
119
+ .good b { display: block; margin-bottom: 4px; }
120
+ .good q, .quote { font-family: var(--mono); font-size: 13px; color: #0b6b4b; quotes: '“' '”'; }
121
+
122
+ .code { background: var(--ink); color: #f1efff; border-radius: 16px; padding: 18px 20px; font-family: var(--mono); font-size: 14px; line-height: 1.7; white-space: pre-wrap; position: relative; border: var(--line); }
123
+ .code .cbtn { position: absolute; top: 10px; right: 10px; }
124
+ .code .c { color: var(--mint); }
125
+ .ftabs { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }
126
+ .ftabs button { font: inherit; font-family: var(--mono); font-size: 13px; font-weight: 600; border: 2px solid var(--ink); background: var(--paper); border-radius: 10px; padding: 5px 10px; cursor: pointer; }
127
+ .ftabs button.on { background: var(--yellow); }
128
+
129
+ table.t { width: 100%; border-collapse: collapse; font-size: 14.5px; }
130
+ table.t td, table.t th { padding: 9px 10px; border-bottom: 2px solid #16142b14; text-align: left; vertical-align: top; }
131
+ table.t th { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
132
+ .err td:first-child { font-family: var(--mono); font-size: 13px; color: #b0164c; }
133
+ .err td:nth-child(2) { font-weight: 700; color: #0b6b4b; }
134
+
135
+ .heat { display: grid; grid-template-columns: 34px repeat(24, 1fr); gap: 3px; font-size: 11px; font-weight: 700; color: var(--muted); }
136
+ .heat i { aspect-ratio: 1; border-radius: 4px; background: #16142b0d; display: block; }
137
+ .hbars { display: flex; align-items: flex-end; gap: 3px; height: 120px; }
138
+ .hbars i { flex: 1; background: var(--blue); border: 2px solid var(--ink); border-radius: 5px 5px 0 0; min-height: 3px; transition: height 1s; }
139
+
140
+ .plan { counter-reset: d; list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; }
141
+ .plan li { counter-increment: d; display: flex; gap: 12px; align-items: center; font-weight: 600; border: 2px solid var(--ink); border-radius: 14px; padding: 10px 14px; background: var(--paper); }
142
+ .plan li::before { content: 'Day ' counter(d); font-weight: 800; background: var(--yellow); border: 2px solid var(--ink); border-radius: 9px; padding: 2px 9px; white-space: nowrap; font-size: 13px; }
143
+ .habit { display: grid; grid-template-columns: 1fr 1fr; border: 2px solid var(--ink); border-radius: 14px; overflow: hidden; margin-top: 10px; }
144
+ .habit div { padding: 11px 14px; font-weight: 600; font-size: 15px; }
145
+ .habit .s { background: var(--soft-mint); } .habit .x { background: #fff1f4; text-decoration: line-through; text-decoration-color: rgba(255,92,138,.6); }
146
+
147
+ .banner { border: var(--line); border-radius: 18px; padding: 16px 20px; background: var(--soft-blue); font-weight: 600; box-shadow: var(--shadow-sm); margin-bottom: 22px; }
148
+ .banner code { background: var(--ink); color: var(--mint); padding: 2px 8px; border-radius: 6px; font-size: 13px; }
149
+
150
+ /* wrapped story */
151
+ .story { position: fixed; inset: 0; z-index: 60; display: none; }
152
+ .story.on { display: block; }
153
+ .slide { position: absolute; inset: 0; display: none; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 60px 24px; overflow: hidden; }
154
+ .slide.on { display: flex; }
155
+ .slide .big { font-size: clamp(64px, 14vw, 170px); font-weight: 800; letter-spacing: -.05em; line-height: .9; animation: zoomIn .8s cubic-bezier(.2,1.4,.4,1) both; }
156
+ .slide .t1 { font-size: clamp(22px, 3.2vw, 36px); font-weight: 800; max-width: 820px; line-height: 1.15; animation: rise .7s .25s both; }
157
+ .slide .t2 { font-size: clamp(16px, 2vw, 21px); font-weight: 600; max-width: 680px; opacity: .85; margin-top: 16px; animation: rise .7s .5s both; line-height: 1.45; }
158
+ .slide .q { font-family: var(--mono); font-size: clamp(14px, 1.8vw, 18px); background: rgba(0,0,0,.18); padding: 14px 18px; border-radius: 14px; max-width: 720px; margin-top: 22px; animation: rise .7s .7s both; text-align: left; }
159
+ @keyframes zoomIn { from { transform: scale(2.2); opacity: 0; } }
160
+ .s-yellow { background: var(--yellow); } .s-pink { background: var(--pink); color: #fff; } .s-blue { background: var(--blue); color: #fff; } .s-mint { background: var(--mint); } .s-ink { background: var(--ink); color: #fff; } .s-purple { background: var(--purple); color: #fff; } .s-orange { background: var(--orange); }
161
+ .story .prog { position: absolute; top: 14px; left: 14px; right: 14px; display: flex; gap: 6px; z-index: 2; }
162
+ .story .prog i { flex: 1; height: 5px; border-radius: 9px; background: rgba(0,0,0,.18); overflow: hidden; }
163
+ .story .prog i b { display: block; height: 100%; width: 0; background: currentColor; }
164
+ .story .prog i.done b { width: 100%; }
165
+ .story .prog i.now b { animation: fillbar var(--dur, 5s) linear forwards; }
166
+ @keyframes fillbar { to { width: 100%; } }
167
+ .story .ctrl { position: absolute; bottom: 22px; left: 0; right: 0; display: flex; justify-content: center; gap: 12px; z-index: 2; }
168
+ .story .tap { position: absolute; top: 0; bottom: 80px; width: 35%; z-index: 1; cursor: pointer; }
169
+ .story .tap.l { left: 0; } .story .tap.r { right: 0; }
170
+ .tools-race { width: min(620px, 92vw); display: grid; gap: 10px; margin-top: 26px; text-align: left; }
171
+ .tools-race div { display: grid; grid-template-columns: 150px 1fr 60px; gap: 10px; align-items: center; font-weight: 800; }
172
+ .tools-race i { height: 26px; background: #fff; border: 3px solid var(--ink); border-radius: 99px; display: block; transform-origin: left; animation: grow 1s cubic-bezier(.2,1,.3,1) both; }
173
+ @keyframes grow { from { transform: scaleX(0); } }
174
+ .counters { display: grid; grid-template-columns: repeat(3, minmax(0, 200px)); gap: 16px; margin-top: 28px; }
175
+ .counters div { background: rgba(255,255,255,.9); color: var(--ink); border: var(--line); border-radius: 18px; padding: 16px; box-shadow: var(--shadow-sm); animation: pop .6s both; }
176
+ .counters b { display: block; font-size: 44px; font-weight: 800; line-height: 1; }
177
+ @media (max-width: 600px) { .counters { grid-template-columns: 1fr 1fr; } .tools-race div { grid-template-columns: 100px 1fr 44px; } }
178
+
179
+ /* practice */
180
+ .timer { position: relative; width: 70px; height: 70px; flex: 0 0 auto; }
181
+ .timer svg { transform: rotate(-90deg); }
182
+ .timer .tt { position: absolute; inset: 0; display: grid; place-items: center; font-weight: 800; font-size: 19px; font-variant-numeric: tabular-nums; }
183
+ .timer.warn .tt { color: var(--pink); }
184
+ .passage { font-size: clamp(19px, 2.3vw, 23px); line-height: 1.6; font-weight: 500; padding: 24px; border: var(--line); border-radius: var(--r); background: #fffdf4; box-shadow: var(--shadow); user-select: none; }
185
+ .passage.poof { animation: poof 1s forwards; }
186
+ @keyframes poof { 100% { filter: blur(18px); opacity: 0; transform: scale(.85) rotate(-2deg); } }
187
+ .broken { font-family: var(--mono); font-size: 15px; background: #fff1f4; border: 2px solid var(--ink); border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; text-decoration: underline wavy var(--pink); text-underline-offset: 5px; user-select: none; }
188
+ textarea, input[type=text] { width: 100%; font: inherit; font-size: 17px; border: var(--line); border-radius: 14px; padding: 12px 14px; background: var(--paper); color: var(--ink); outline: none; }
189
+ textarea { min-height: 120px; resize: vertical; line-height: 1.5; }
190
+ textarea:focus, input:focus { box-shadow: var(--shadow-sm); }
191
+ .flow { display: flex; align-items: center; justify-content: center; padding: 22px 0; }
192
+ .flow .box { flex: 1 1 0; max-width: 220px; min-height: 130px; border: var(--line); border-radius: 18px; padding: 14px 10px; text-align: center; box-shadow: var(--shadow-sm); font-weight: 800; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; animation: pop .6s both; }
193
+ .flow .box .e { font-size: 36px; }
194
+ .flow .b0 { background: var(--yellow); } .flow .b1 { background: var(--soft-pink); animation-delay: .5s; } .flow .b2 { background: var(--mint); animation-delay: 1s; }
195
+ .flow .arrow { flex: 0 0 44px; text-align: center; font-size: 26px; font-weight: 800; }
196
+ @media (max-width: 640px) { .flow { flex-direction: column; } .flow .box { max-width: none; width: 100%; min-height: 0; } .flow .arrow { transform: rotate(90deg); } }
197
+ .original { font-family: var(--mono); font-size: 15px; background: var(--ink); color: #f5f3ff; border-radius: 16px; padding: 16px 18px; line-height: 1.6; user-select: none; }
198
+ .original::before { content: 'you › '; color: var(--mint); font-weight: 700; }
199
+ .meter { border: 2px solid var(--ink); border-radius: 99px; padding: 3px 11px; font-weight: 700; font-size: 13px; background: var(--paper); }
200
+ .meter.ok { background: var(--mint); } .meter.bad { background: var(--soft-pink); }
201
+ .target { display: inline-flex; gap: 8px; background: var(--ink); color: #fff; border-radius: 12px; padding: 7px 13px; font-weight: 700; font-size: 14px; margin-bottom: 14px; }
202
+ .target b { color: var(--yellow); }
203
+ .cmp { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 10px 0; }
204
+ .cmp > div { border: 2px solid var(--ink); border-radius: 12px; padding: 10px 12px; font-size: 15px; line-height: 1.5; }
205
+ .cmp .y { background: #fff7e0; } .cmp .m { background: var(--soft-mint); }
206
+ .cmp small { display: block; font-weight: 800; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 4px; }
207
+ @media (max-width: 640px) { .cmp { grid-template-columns: 1fr; } }
208
+
209
+ /* prompts explorer */
210
+ .plist .p { border: 2px solid var(--ink); border-radius: 14px; padding: 12px 14px; background: var(--paper); margin-top: 10px; }
211
+ .plist .p .m { font-size: 12px; font-weight: 700; color: var(--muted); margin-bottom: 6px; display: flex; gap: 10px; flex-wrap: wrap; }
212
+ .plist .p .x { font-family: var(--mono); font-size: 13.5px; white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow: auto; }
213
+
214
+ .overlay { position: fixed; inset: 0; background: rgba(22,20,43,.78); display: none; place-items: center; z-index: 80; color: #fff; text-align: center; }
215
+ .overlay.on { display: grid; }
216
+ .dots { display: flex; gap: 12px; justify-content: center; margin-bottom: 22px; }
217
+ .dots i { width: 24px; height: 24px; border-radius: 50%; border: 3px solid var(--ink); animation: hop .9s ease-in-out infinite; }
218
+ .dots i:nth-child(1) { background: var(--yellow); } .dots i:nth-child(2) { background: var(--pink); animation-delay: .15s; } .dots i:nth-child(3) { background: var(--mint); animation-delay: .3s; } .dots i:nth-child(4) { background: var(--blue); animation-delay: .45s; }
219
+ @keyframes hop { 50% { transform: translateY(-24px); } }
220
+ .toast { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%) translateY(200%); opacity: 0; background: var(--ink); color: #fff; padding: 13px 20px; border-radius: 15px; font-weight: 700; z-index: 90; transition: all .35s cubic-bezier(.2,1.5,.4,1); border: 3px solid var(--yellow); max-width: calc(100% - 32px); }
221
+ .toast.on { transform: translateX(-50%); opacity: 1; }
222
+ #confetti { position: fixed; inset: 0; pointer-events: none; z-index: 95; }
223
+ .foot { text-align: center; color: var(--muted); font-weight: 600; font-size: 14px; margin-top: 60px; }
224
+ canvas.cardprev { width: 100%; max-width: 720px; border: var(--line); border-radius: 18px; box-shadow: var(--shadow); display: block; }
225
+ @media print { .nav, .blob, .no-print, body::before { display: none !important; } .card { box-shadow: none; } }
226
+
227
+ .fixes { padding-left: 22px; margin: 0; } .fixes li { font-weight: 600; line-height: 1.5; margin-bottom: 10px; }