privateboard 0.1.0 → 0.1.2

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.
@@ -63,16 +63,71 @@
63
63
  height: 100vh;
64
64
  overflow: hidden;
65
65
  }
66
+ /* Body becomes a flex column so the optional system-notice banner
67
+ can sit above the .control shell without forcing total height to
68
+ exceed the viewport. .control keeps its existing internal grid;
69
+ it just lives inside this outer flex now. */
70
+ body {
71
+ display: flex;
72
+ flex-direction: column;
73
+ min-height: 0;
74
+ }
66
75
 
67
76
  ::-webkit-scrollbar { width: 5px; height: 5px; }
68
77
  ::-webkit-scrollbar-track { background: var(--bg); }
69
78
  ::-webkit-scrollbar-thumb { background: var(--line-bright); }
70
79
 
80
+ /* ─────────── SYSTEM NOTICE BANNER ───────────
81
+ Dismissible single-line notice that appears above the app shell
82
+ when storage migrations have been applied. Mono micro-type, lime
83
+ left chevron, hairline border under so it reads as system chrome
84
+ not chat content. Only shown when app.js sets data-sys-notice
85
+ visible. */
86
+ .sys-notice {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 10px;
90
+ padding: 8px 14px;
91
+ background: var(--panel-2, #1A1A18);
92
+ border-bottom: 0.5px solid var(--lime-dim, #2D5532);
93
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
94
+ font-size: 11.5px;
95
+ color: var(--text-soft, #8E8B83);
96
+ flex-shrink: 0;
97
+ }
98
+ .sys-notice[hidden] { display: none; }
99
+ .sys-notice-mark {
100
+ color: var(--lime, #6FB572);
101
+ font-weight: 700;
102
+ }
103
+ .sys-notice-text {
104
+ flex: 1 1 auto;
105
+ min-width: 0;
106
+ line-height: 1.4;
107
+ }
108
+ .sys-notice-text .sys-notice-strong {
109
+ color: var(--text, #C8C5BE);
110
+ font-weight: 600;
111
+ }
112
+ .sys-notice-close {
113
+ appearance: none;
114
+ background: none;
115
+ border: 0;
116
+ color: var(--text-faint, #5C5A4D);
117
+ cursor: pointer;
118
+ font-size: 13px;
119
+ line-height: 1;
120
+ padding: 4px 6px;
121
+ transition: color 0.12s;
122
+ }
123
+ .sys-notice-close:hover { color: var(--lime, #6FB572); }
124
+
71
125
  /* ─────────── ROOT ─────────── */
72
126
  .control {
73
127
  display: grid;
74
128
  grid-template-rows: auto 1fr;
75
- height: 100vh;
129
+ flex: 1 1 auto;
130
+ min-height: 0;
76
131
  gap: 8px;
77
132
  padding: 8px;
78
133
  }
@@ -141,98 +196,6 @@
141
196
  .topbar-right .val { color: var(--text-soft); }
142
197
  @keyframes pulse { 0%, 60% { opacity: 1; } 80% { opacity: 0.4; } }
143
198
 
144
- .user-menu-wrap { position: relative; flex: 1; min-width: 0; }
145
- .user-menu {
146
- position: absolute;
147
- /* Default opens DOWNWARD; sidebar-foot variant flips upward below */
148
- top: calc(100% + 6px);
149
- right: 0;
150
- min-width: 200px;
151
- background: var(--panel);
152
- border: 0.5px solid var(--line-bright);
153
- z-index: 100;
154
- opacity: 0;
155
- visibility: hidden;
156
- transform: translateY(-2px);
157
- transition: opacity 0.12s, transform 0.12s, visibility 0s linear 0.12s;
158
- }
159
- .user-menu-wrap:hover .user-menu,
160
- .user-menu-wrap:focus-within .user-menu {
161
- opacity: 1;
162
- visibility: visible;
163
- transform: translateY(0);
164
- transition: opacity 0.12s, transform 0.12s, visibility 0s;
165
- }
166
-
167
- /* Sidebar-foot variant: menu pops UPWARD from the bottom user-block,
168
- anchored to the left edge so it sits inside the sidebar column. */
169
- .sidebar-foot .user-menu-wrap { display: flex; }
170
- .sidebar-foot .user-block { flex: 1; min-width: 0; }
171
- .sidebar-foot .user-menu {
172
- top: auto;
173
- bottom: calc(100% + 6px);
174
- right: auto;
175
- left: 0;
176
- transform: translateY(2px);
177
- }
178
- .sidebar-foot .user-menu-wrap:hover .user-menu,
179
- .sidebar-foot .user-menu-wrap:focus-within .user-menu {
180
- transform: translateY(0);
181
- }
182
- .sidebar-foot .user-block .chev {
183
- margin-left: auto;
184
- color: var(--text-dim);
185
- font-size: 9px;
186
- transition: color 0.1s;
187
- }
188
- .sidebar-foot .user-menu-wrap:hover .user-block .chev,
189
- .sidebar-foot .user-menu-wrap:focus-within .user-block .chev {
190
- color: var(--lime);
191
- }
192
-
193
- .user-menu-section {
194
- padding: 8px 12px;
195
- border-bottom: 0.5px solid var(--line);
196
- background: var(--panel-2);
197
- }
198
- .user-menu-section .name {
199
- font-size: 11px;
200
- color: var(--text);
201
- font-weight: 700;
202
- margin-bottom: 1px;
203
- }
204
- .user-menu-section .meta {
205
- font-size: 9.5px;
206
- color: var(--text-dim);
207
- letter-spacing: 0.04em;
208
- }
209
- .user-menu-section .meta .lime { color: var(--lime); }
210
-
211
- .user-menu-item {
212
- display: flex;
213
- align-items: center;
214
- gap: 10px;
215
- padding: 8px 12px;
216
- font-size: 11.5px;
217
- color: var(--text-soft);
218
- text-decoration: none;
219
- border-bottom: 0.5px solid var(--line);
220
- transition: all 0.08s;
221
- }
222
- .user-menu-item:last-child { border-bottom: none; }
223
- .user-menu-item:hover {
224
- background: var(--panel-2);
225
- color: var(--lime);
226
- }
227
- .user-menu-item .menu-icon {
228
- width: 14px;
229
- color: var(--text-dim);
230
- text-align: center;
231
- }
232
- .user-menu-item:hover .menu-icon { color: var(--lime); }
233
- .user-menu-item.danger:hover { color: var(--red); }
234
- .user-menu-item.danger:hover .menu-icon { color: var(--red); }
235
-
236
199
  /* ═══════════════════════════════════════════
237
200
  MAIN GRID — sidebar + content
238
201
  ═══════════════════════════════════════════ */
@@ -661,9 +624,10 @@
661
624
  }
662
625
  .new-btn:hover {
663
626
  background: var(--panel-2);
664
- color: var(--text);
627
+ color: var(--lime);
665
628
  }
666
- .new-btn:hover::before { color: var(--lime); }
629
+ /* The ::before icon inherits via currentColor — no explicit
630
+ hover override needed; setting the parent's color cascades. */
667
631
  /* Active state · matches session-row-shell.active treatment so the
668
632
  sidebar reads consistently when the composer is the current view. */
669
633
  .new-btn.active {
@@ -1838,8 +1802,10 @@
1838
1802
  .paused-bar-text { font-size: 10.5px; color: var(--text-dim); }
1839
1803
  .paused-bar-text strong { color: var(--text); font-weight: 700; }
1840
1804
  .paused-bar-text .lime { color: var(--lime); }
1841
- .paused-bar-actions { display: flex; gap: 6px; flex-wrap: wrap; }
1805
+ .paused-bar-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
1842
1806
  .resume-btn-lg {
1807
+ display: inline-flex;
1808
+ align-items: center;
1843
1809
  padding: 6px 12px;
1844
1810
  background: var(--lime);
1845
1811
  color: var(--bg);
@@ -1847,6 +1813,7 @@
1847
1813
  font-family: var(--mono);
1848
1814
  font-size: 10px;
1849
1815
  font-weight: 700;
1816
+ line-height: 1.2;
1850
1817
  cursor: pointer;
1851
1818
  text-decoration: none;
1852
1819
  text-transform: uppercase;
@@ -1935,17 +1902,26 @@
1935
1902
  }
1936
1903
  .followup-btn:hover { background: var(--bg); color: var(--lime); }
1937
1904
  .ghost-btn {
1938
- padding: 5px 10px;
1905
+ /* inline-flex + align-items:center so text always centers vertically
1906
+ inside the box even when a flex parent stretches us to match the
1907
+ resume-btn-lg sibling's height. Padding / font-size / weight are
1908
+ lined up with .resume-btn-lg so the buttons read as the same
1909
+ size class — only the fill / border treatment distinguishes the
1910
+ primary lime CTA from a quieter ghost. */
1911
+ display: inline-flex;
1912
+ align-items: center;
1913
+ padding: 6px 12px;
1939
1914
  background: var(--bg);
1940
1915
  border: 0.5px solid var(--line-strong);
1941
1916
  font-family: var(--mono);
1942
- font-size: 9.5px;
1943
- font-weight: 600;
1917
+ font-size: 10px;
1918
+ font-weight: 700;
1919
+ line-height: 1.2;
1944
1920
  color: var(--text);
1945
1921
  cursor: pointer;
1946
1922
  text-decoration: none;
1947
1923
  text-transform: uppercase;
1948
- letter-spacing: 0.08em;
1924
+ letter-spacing: 0.1em;
1949
1925
  }
1950
1926
  .ghost-btn:hover { border-color: var(--lime); color: var(--lime); }
1951
1927
 
@@ -3850,6 +3826,37 @@
3850
3826
  .msg-bubble p:last-child { margin-bottom: 0; }
3851
3827
  .msg-bubble strong { color: var(--text); font-weight: 600; }
3852
3828
  .msg-bubble em { font-style: normal; color: var(--lime); font-weight: 500; }
3829
+ /* Markdown blockquote · designed inset card, not a raw `>` line.
3830
+ Per the no-coloured-left-borders rule, the callout treatment
3831
+ uses a top mono kicker + panel-2 surface + indent (NOT a left
3832
+ border). Italic body sets it apart from the surrounding agent
3833
+ prose; the inline @director attribution at the end (rendered
3834
+ by inline()'s @-mention pass) gets its usual lime treatment.
3835
+ Used by quote-cta probe / second messages and any plain
3836
+ markdown blockquote a director / chair might emit. */
3837
+ .msg-bubble .msg-quote {
3838
+ margin: 0 0 12px;
3839
+ padding: 10px 14px;
3840
+ background: var(--panel-2);
3841
+ font-family: var(--font-agent);
3842
+ font-style: italic;
3843
+ font-size: 13px;
3844
+ line-height: 1.55;
3845
+ color: var(--text-soft);
3846
+ }
3847
+ .msg-bubble .msg-quote::before {
3848
+ content: "// quoted";
3849
+ display: block;
3850
+ font-family: var(--mono);
3851
+ font-size: 9px;
3852
+ font-weight: 700;
3853
+ letter-spacing: 0.18em;
3854
+ text-transform: uppercase;
3855
+ color: var(--text-faint);
3856
+ font-style: normal;
3857
+ margin-bottom: 6px;
3858
+ }
3859
+ .msg-bubble .msg-quote:last-child { margin-bottom: 0; }
3853
3860
  /* Markdown tables · editorial style. No outer box, no zebra, no
3854
3861
  uppercase data-grid headers — those read as "spreadsheet pasted
3855
3862
  into a conversation" and broke the bubble's literary register.
@@ -6991,6 +6998,8 @@
6991
6998
  <script src="auto-hide-scroll.js" defer></script>
6992
6999
  <link rel="stylesheet" href="onboarding.css">
6993
7000
  <script src="onboarding.js" defer></script>
7001
+ <link rel="stylesheet" href="quote-cta.css">
7002
+ <script src="quote-cta.js" defer></script>
6994
7003
  <script src="app.js" defer></script>
6995
7004
  <script>
6996
7005
  /* FOUC-prevention: apply saved theme synchronously before paint */
@@ -7007,6 +7016,19 @@
7007
7016
  </head>
7008
7017
  <body>
7009
7018
 
7019
+ <!-- ═══════════════ SYSTEM NOTICE BANNER ═══════════════
7020
+ Surfaces "storage upgraded" notices when the user opens a build
7021
+ that ran new schema migrations against their existing DB. Hidden
7022
+ by default; populated + un-hidden by app.js after the boot fetch
7023
+ to /api/system/migrations resolves. Dismiss writes the latest
7024
+ migration name to localStorage so it doesn't re-show until the
7025
+ next time fresh migrations actually apply. -->
7026
+ <div class="sys-notice" data-sys-notice hidden>
7027
+ <span class="sys-notice-mark">▸</span>
7028
+ <span class="sys-notice-text" data-sys-notice-text></span>
7029
+ <button type="button" class="sys-notice-close" data-sys-notice-close aria-label="Dismiss">✕</button>
7030
+ </div>
7031
+
7010
7032
  <div class="control">
7011
7033
 
7012
7034
  <!-- ═══════════════ TOP BAR (classification banner) ═══════════════ -->
@@ -7089,21 +7111,13 @@
7089
7111
  </div><!-- /agents panel -->
7090
7112
 
7091
7113
  <div class="sidebar-foot">
7092
- <div class="user-menu-wrap" tabindex="0">
7093
- <a href="#" class="user-block">
7094
- <div class="user-av" data-user-avatar>K</div>
7095
- <div class="user-info">
7096
- <div class="user-name" data-user-name>—</div>
7097
- <div class="user-meta" data-user-meta>// host</div>
7098
- </div>
7099
- <span class="chev">▴</span>
7100
- </a>
7101
- <div class="user-menu">
7102
- <div class="user-menu-section">
7103
- <div class="name" data-user-menu-name>—</div>
7104
- </div>
7114
+ <a href="#" class="user-block">
7115
+ <div class="user-av" data-user-avatar>K</div>
7116
+ <div class="user-info">
7117
+ <div class="user-name" data-user-name>—</div>
7118
+ <div class="user-meta" data-user-meta>// host</div>
7105
7119
  </div>
7106
- </div>
7120
+ </a>
7107
7121
  <a href="#" class="icon-btn" title="Settings" data-user-settings-trigger>⚙</a>
7108
7122
  </div>
7109
7123
 
@@ -7171,8 +7185,7 @@
7171
7185
  <strong>// room adjourned.</strong> the brief is filed above.
7172
7186
  </div>
7173
7187
  <div class="adjourned-bar-actions">
7174
- <a href="#" class="ghost-btn">[↓] Export</a>
7175
- <a href="#" class="ghost-btn">[↗] Share</a>
7188
+ <a href="#" class="ghost-btn" data-room-export>[↓] Export</a>
7176
7189
  </div>
7177
7190
  </footer>
7178
7191
  </div>
@@ -84,9 +84,9 @@
84
84
  {
85
85
  tag: "// pivot",
86
86
  text: "Six months of runway, real users but flat MRR — pivot the product, hold the line, or shut it down?",
87
- hint: "no-mercy room: force the load-bearing claim into the open",
88
- tone: "no-mercy",
89
- intensity: "brutal",
87
+ hint: "critique room: each director audits the plan blocker / major / minor",
88
+ tone: "critique",
89
+ intensity: "sharp",
90
90
  briefStyle: "auto",
91
91
  agents: ["socrates", "first-principles", "long-horizon"],
92
92
  },
@@ -729,7 +729,7 @@
729
729
 
730
730
  function show() {
731
731
  if (document.getElementById("onb-overlay")) return;
732
- // Claim the dashboard sub-state · prototype-dashboard.html runs a
732
+ // Claim the dashboard sub-state · the dashboard page runs a
733
733
  // restore tick (~2.5s of 250ms retries) that re-opens whatever
734
734
  // agent profile the user last viewed once refreshAgents mounts the
735
735
  // sidebar rows. Onboarding always lands the user on a fresh room
@@ -0,0 +1,225 @@
1
+ /* ═══════════════════════════════════════════
2
+ QUOTE CTA · selection-driven follow-up
3
+ ═══════════════════════════════════════════
4
+ When the user selects text inside a director's message bubble,
5
+ a small floating bar appears above the selection with two
6
+ actions: ask a follow-up (opens an overlay), or react with
7
+ "love it" (one-click, no typing). Both produce a user message
8
+ that quotes the selected snippet via markdown blockquote.
9
+
10
+ Visual vocabulary mirrors the rest of the app: panel-2 surface,
11
+ lime accent, mono micro-type for the action labels. Per the
12
+ no-coloured-left-borders rule, callouts use top tags + indent
13
+ instead of border-left treatments. */
14
+
15
+ /* Floating action bar · two segmented buttons above the selection.
16
+ Restrained surface · panel-2 base, neutral hairline border, soft
17
+ shadow for elevation. No lime border, no notch · the bar is just
18
+ text-and-icon, not a feature. Inter-button divider is structural
19
+ border-right between adjacent items, not a callout treatment. */
20
+ .qcta {
21
+ position: absolute;
22
+ z-index: 1400;
23
+ display: none;
24
+ background: var(--panel-2, #1A1A18);
25
+ border: 0.5px solid var(--line-strong, #3A3A35);
26
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
27
+ box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.55);
28
+ white-space: nowrap;
29
+ user-select: none;
30
+ }
31
+ .qcta.open { display: inline-flex; align-items: stretch; }
32
+
33
+ .qcta-btn {
34
+ appearance: none;
35
+ background: transparent;
36
+ border: 0;
37
+ color: var(--text, #C8C5BE);
38
+ font-family: inherit;
39
+ font-size: 12px;
40
+ font-weight: 500;
41
+ letter-spacing: 0;
42
+ padding: 7px 13px;
43
+ cursor: pointer;
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: 6px;
47
+ transition: color 0.12s;
48
+ }
49
+ /* Tighten the inner edge of each button so the two CTAs sit close
50
+ together. Outer edges keep their 13px breathing room against the
51
+ bar border. */
52
+ .qcta-btn:not(:last-child) { padding-right: 6px; }
53
+ .qcta-btn:not(:first-child) { padding-left: 6px; }
54
+ .qcta-btn:hover { color: var(--lime, #6FB572); }
55
+ .qcta-btn:hover .ico { color: var(--lime, #6FB572); }
56
+ .qcta-btn .ico {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ font-size: 12.5px;
60
+ font-weight: 500;
61
+ line-height: 1;
62
+ color: var(--text-soft, #8E8B83);
63
+ transition: color 0.12s;
64
+ }
65
+ .qcta-btn .ico svg { display: block; }
66
+
67
+ /* Read-only hint · shown only when the bar is in `qcta-readonly`
68
+ state (adjourned room). The buttons collapse out of the row and
69
+ the hint takes their place — the bar still pops up to acknowledge
70
+ the user's selection, but explains why probe / second can't fire. */
71
+ .qcta-hint {
72
+ display: none;
73
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
74
+ font-size: 11px;
75
+ font-weight: 500;
76
+ letter-spacing: 0.04em;
77
+ color: var(--text-soft, #8E8B83);
78
+ padding: 8px 13px;
79
+ white-space: nowrap;
80
+ }
81
+ .qcta.qcta-readonly .qcta-btn { display: none; }
82
+ .qcta.qcta-readonly .qcta-hint { display: inline-flex; align-items: center; }
83
+
84
+ /* Ask-follow-up overlay · backdrop + modal. Same chrome family
85
+ as openSendChoiceModal (.pc-overlay) so the family stays coherent. */
86
+ .qask-overlay {
87
+ position: fixed;
88
+ inset: 0;
89
+ background: rgba(8, 8, 8, 0.5);
90
+ -webkit-backdrop-filter: blur(8px) saturate(1.05);
91
+ backdrop-filter: blur(8px) saturate(1.05);
92
+ z-index: 1500;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ padding: 24px;
97
+ animation: qask-fade 0.16s ease-out;
98
+ }
99
+ @keyframes qask-fade { from { opacity: 0; } to { opacity: 1; } }
100
+
101
+ .qask-modal {
102
+ width: 100%;
103
+ max-width: 560px;
104
+ background: var(--panel, #131312);
105
+ border: 0.5px solid var(--lime, #6FB572);
106
+ color: var(--text, #C8C5BE);
107
+ display: flex;
108
+ flex-direction: column;
109
+ box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.65);
110
+ animation: qask-rise 0.2s ease-out;
111
+ }
112
+ @keyframes qask-rise {
113
+ from { transform: translateY(8px); opacity: 0; }
114
+ to { transform: translateY(0); opacity: 1; }
115
+ }
116
+
117
+ .qask-classification {
118
+ padding: 8px 14px;
119
+ border-bottom: 0.5px solid var(--line-bright, #2A2A26);
120
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
121
+ font-size: 9px;
122
+ letter-spacing: 0.18em;
123
+ text-transform: uppercase;
124
+ color: var(--text-faint, #3A382F);
125
+ display: flex;
126
+ justify-content: space-between;
127
+ align-items: center;
128
+ background: var(--panel-2, #1A1A18);
129
+ }
130
+ .qask-classification .dot { color: var(--lime, #6FB572); }
131
+ .qask-classification .right { color: var(--text-faint, #3A382F); }
132
+
133
+ .qask-body { padding: 18px 18px 14px; display: flex; flex-direction: column; gap: 12px; }
134
+
135
+ /* Quote callout · top tag + indented italic body. NO left-border
136
+ treatment per the project-wide rule. */
137
+ .qask-quote {
138
+ background: var(--panel-2, #1A1A18);
139
+ padding: 12px 14px;
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: 6px;
143
+ max-height: 180px;
144
+ overflow-y: auto;
145
+ }
146
+ .qask-quote-tag {
147
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
148
+ font-size: 9px;
149
+ letter-spacing: 0.18em;
150
+ text-transform: uppercase;
151
+ color: var(--text-faint, #3A382F);
152
+ font-weight: 700;
153
+ }
154
+ .qask-quote-body {
155
+ font-size: 12.5px;
156
+ line-height: 1.55;
157
+ color: var(--text-soft, #8E8B83);
158
+ font-style: italic;
159
+ white-space: pre-wrap;
160
+ word-break: break-word;
161
+ }
162
+
163
+ .qask-input-wrap {
164
+ border: 0.5px solid var(--line-strong, #3A3A35);
165
+ background: var(--bg, #0A0A0A);
166
+ display: flex;
167
+ align-items: stretch;
168
+ transition: border-color 0.12s;
169
+ }
170
+ .qask-input-wrap:focus-within { border-color: var(--lime, #6FB572); }
171
+ .qask-input-wrap::before {
172
+ content: ">";
173
+ color: var(--lime, #6FB572);
174
+ font-weight: 700;
175
+ font-size: 13px;
176
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
177
+ padding: 9px 0 0 11px;
178
+ align-self: flex-start;
179
+ }
180
+ .qask-input {
181
+ flex: 1;
182
+ border: none;
183
+ background: transparent;
184
+ font-family: var(--font-human, var(--mono));
185
+ font-size: 13.5px;
186
+ color: var(--text, #C8C5BE);
187
+ outline: none;
188
+ padding: 9px 12px;
189
+ letter-spacing: -0.003em;
190
+ width: 100%;
191
+ resize: none;
192
+ min-height: 72px;
193
+ line-height: 1.5;
194
+ }
195
+ .qask-input::placeholder { color: var(--text-faint, #3A382F); }
196
+
197
+ .qask-foot {
198
+ padding: 10px 14px;
199
+ border-top: 0.5px solid var(--line-bright, #2A2A26);
200
+ background: var(--panel-2, #1A1A18);
201
+ display: flex;
202
+ justify-content: flex-end;
203
+ align-items: center;
204
+ gap: 8px;
205
+ }
206
+ .qask-btn {
207
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
208
+ font-size: 10px;
209
+ font-weight: 700;
210
+ text-transform: uppercase;
211
+ letter-spacing: 0.1em;
212
+ padding: 7px 14px;
213
+ border: 0.5px solid var(--line-strong, #3A3A35);
214
+ background: transparent;
215
+ color: var(--text-soft, #8E8B83);
216
+ cursor: pointer;
217
+ transition: border-color 0.12s, color 0.12s, background 0.12s;
218
+ }
219
+ .qask-btn:hover { border-color: var(--lime, #6FB572); color: var(--lime, #6FB572); }
220
+ .qask-btn.primary {
221
+ border-color: var(--lime, #6FB572);
222
+ color: var(--lime, #6FB572);
223
+ }
224
+ .qask-btn.primary:hover { background: var(--lime, #6FB572); color: var(--bg, #0A0A0A); }
225
+ .qask-btn[disabled] { opacity: 0.4; cursor: not-allowed; }