rcas 0.2.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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/CITATION.cff +17 -0
  3. data/DESIGN.md +783 -0
  4. data/LICENSE +21 -0
  5. data/MANUAL.md +6265 -0
  6. data/README.md +267 -0
  7. data/bin/rcas +9 -0
  8. data/bin/rcas-app +9 -0
  9. data/bin/rcas-chat +9 -0
  10. data/lib/rcas/algebraic.rb +481 -0
  11. data/lib/rcas/analysis.rb +966 -0
  12. data/lib/rcas/app/launcher.rb +203 -0
  13. data/lib/rcas/app/public/app.css +402 -0
  14. data/lib/rcas/app/public/app.js +449 -0
  15. data/lib/rcas/app/public/index.html +46 -0
  16. data/lib/rcas/app/server.rb +220 -0
  17. data/lib/rcas/app/window.rb +94 -0
  18. data/lib/rcas/app/worksheet.rb +290 -0
  19. data/lib/rcas/app.rb +168 -0
  20. data/lib/rcas/background.rb +758 -0
  21. data/lib/rcas/chat/assistant.rb +199 -0
  22. data/lib/rcas/chat/picker.rb +164 -0
  23. data/lib/rcas/chat/repl.rb +583 -0
  24. data/lib/rcas/chat/session.rb +137 -0
  25. data/lib/rcas/chat/settings.rb +71 -0
  26. data/lib/rcas/chat/style.rb +30 -0
  27. data/lib/rcas/chat/tool.rb +53 -0
  28. data/lib/rcas/chat/ui.rb +316 -0
  29. data/lib/rcas/chat/usage.rb +62 -0
  30. data/lib/rcas/chat/workspace.rb +132 -0
  31. data/lib/rcas/chat.rb +54 -0
  32. data/lib/rcas/coefficients.rb +170 -0
  33. data/lib/rcas/combinatorics.rb +274 -0
  34. data/lib/rcas/complex_parts.rb +160 -0
  35. data/lib/rcas/constants.rb +129 -0
  36. data/lib/rcas/core_ext.rb +35 -0
  37. data/lib/rcas/decide.rb +501 -0
  38. data/lib/rcas/decompositions.rb +241 -0
  39. data/lib/rcas/differentiate.rb +144 -0
  40. data/lib/rcas/discussion.rb +558 -0
  41. data/lib/rcas/distributions.rb +980 -0
  42. data/lib/rcas/dixon.rb +95 -0
  43. data/lib/rcas/docs.rb +321 -0
  44. data/lib/rcas/domains.rb +728 -0
  45. data/lib/rcas/expand.rb +174 -0
  46. data/lib/rcas/expression.rb +613 -0
  47. data/lib/rcas/factor.rb +605 -0
  48. data/lib/rcas/finite_field.rb +577 -0
  49. data/lib/rcas/fourier.rb +118 -0
  50. data/lib/rcas/fps.rb +678 -0
  51. data/lib/rcas/fraction.rb +126 -0
  52. data/lib/rcas/functions.rb +1136 -0
  53. data/lib/rcas/gcd.rb +112 -0
  54. data/lib/rcas/geometry.rb +266 -0
  55. data/lib/rcas/groebner.rb +162 -0
  56. data/lib/rcas/hold.rb +277 -0
  57. data/lib/rcas/hypothesis.rb +364 -0
  58. data/lib/rcas/inequalities.rb +689 -0
  59. data/lib/rcas/integral_functions.rb +260 -0
  60. data/lib/rcas/integrate.rb +1589 -0
  61. data/lib/rcas/integrate_substitutions.rb +434 -0
  62. data/lib/rcas/interpolate.rb +40 -0
  63. data/lib/rcas/irb.rb +146 -0
  64. data/lib/rcas/laplace.rb +159 -0
  65. data/lib/rcas/latex.rb +556 -0
  66. data/lib/rcas/lattice.rb +172 -0
  67. data/lib/rcas/linear_algebra.rb +117 -0
  68. data/lib/rcas/linear_program.rb +416 -0
  69. data/lib/rcas/lint.rb +79 -0
  70. data/lib/rcas/matrix.rb +531 -0
  71. data/lib/rcas/matrix_multiply.rb +202 -0
  72. data/lib/rcas/multimodular.rb +286 -0
  73. data/lib/rcas/named_polynomials.rb +274 -0
  74. data/lib/rcas/number_theory.rb +443 -0
  75. data/lib/rcas/numerics.rb +825 -0
  76. data/lib/rcas/ode.rb +488 -0
  77. data/lib/rcas/openmath/objects.rb +364 -0
  78. data/lib/rcas/openmath/phrasebook.rb +551 -0
  79. data/lib/rcas/openmath/popcorn.rb +518 -0
  80. data/lib/rcas/openmath/xml.rb +309 -0
  81. data/lib/rcas/openmath.rb +49 -0
  82. data/lib/rcas/petkovsek.rb +165 -0
  83. data/lib/rcas/piecewise.rb +488 -0
  84. data/lib/rcas/plot.rb +763 -0
  85. data/lib/rcas/plot3d.rb +419 -0
  86. data/lib/rcas/poly_matrix.rb +318 -0
  87. data/lib/rcas/poly_recurrence.rb +117 -0
  88. data/lib/rcas/polynomial.rb +466 -0
  89. data/lib/rcas/precision.rb +925 -0
  90. data/lib/rcas/printer.rb +150 -0
  91. data/lib/rcas/product.rb +155 -0
  92. data/lib/rcas/q_difference.rb +296 -0
  93. data/lib/rcas/q_functions.rb +158 -0
  94. data/lib/rcas/q_summation.rb +308 -0
  95. data/lib/rcas/q_zeilberger.rb +199 -0
  96. data/lib/rcas/random.rb +506 -0
  97. data/lib/rcas/rational_function.rb +186 -0
  98. data/lib/rcas/recurrence.rb +323 -0
  99. data/lib/rcas/render.rb +431 -0
  100. data/lib/rcas/results.rb +192 -0
  101. data/lib/rcas/scalar.rb +219 -0
  102. data/lib/rcas/series.rb +726 -0
  103. data/lib/rcas/simplify.rb +649 -0
  104. data/lib/rcas/solve.rb +2002 -0
  105. data/lib/rcas/special.rb +163 -0
  106. data/lib/rcas/statistics.rb +175 -0
  107. data/lib/rcas/steps.rb +835 -0
  108. data/lib/rcas/summation.rb +532 -0
  109. data/lib/rcas/trig.rb +264 -0
  110. data/lib/rcas/van_hoeij.rb +241 -0
  111. data/lib/rcas/vector.rb +175 -0
  112. data/lib/rcas/vector_calculus.rb +411 -0
  113. data/lib/rcas/version.rb +5 -0
  114. data/lib/rcas/zeilberger.rb +358 -0
  115. data/lib/rcas.rb +91 -0
  116. data/package.json +8 -0
  117. metadata +206 -0
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "rbconfig"
5
+
6
+ module RCAS
7
+ module App
8
+ # The desktop entries that make rcas-app look like an installed
9
+ # application: an icon in the Dock, in the Start menu or in the
10
+ # activities overview, that starts the program without a terminal.
11
+ #
12
+ # bin/rcas-app --install # put them where the desktop looks
13
+ # bin/rcas-app --uninstall # take them away again
14
+ #
15
+ # Each platform wants a different thing and each is a few lines of
16
+ # text, so all three are written here rather than shipped as files:
17
+ #
18
+ # * macOS: an application bundle, rcas.app in ~/Applications, whose
19
+ # executable is a shell script that runs bin/rcas-app. `sips` and
20
+ # `iconutil` (both part of macOS) turn the logo into the .icns the
21
+ # Dock wants; without them the bundle simply has the default icon.
22
+ # * Linux: a .desktop entry [FDO14] in ~/.local/share/applications.
23
+ # StartupWMClass matches the --class=rcas the window is opened with,
24
+ # so the running window is grouped under this icon and not under the
25
+ # browser's.
26
+ # * Windows: a shortcut in the Start menu, written by the one piece of
27
+ # Windows scripting that is always installed (WScript.Shell through
28
+ # PowerShell); if that fails, a .cmd file in the same place, which
29
+ # works as well and only looks plainer.
30
+ module Launcher
31
+ IDENTIFIER = "org.rcas.app"
32
+
33
+ module_function
34
+
35
+ def macos? = Window.macos?
36
+ def windows? = Window.windows?
37
+
38
+ # Where the entry goes, per platform.
39
+ def destination
40
+ if macos?
41
+ File.join(Dir.home, "Applications", "rcas.app")
42
+ elsif windows?
43
+ File.join(start_menu, "rcas.lnk")
44
+ else
45
+ File.join(ENV.fetch("XDG_DATA_HOME", File.join(Dir.home, ".local", "share")), "applications", "rcas.desktop")
46
+ end
47
+ end
48
+
49
+ def start_menu
50
+ base = ENV["APPDATA"] || File.join(Dir.home, "AppData", "Roaming")
51
+ File.join(base, "Microsoft", "Windows", "Start Menu", "Programs")
52
+ end
53
+
54
+ # The command the entry runs: this Ruby, this checkout.
55
+ def command = [RbConfig.ruby, File.join(App.root, "bin", "rcas-app")]
56
+
57
+ def install
58
+ path = destination
59
+ FileUtils.mkdir_p(File.dirname(path))
60
+ if macos? then install_bundle(path)
61
+ elsif windows? then install_shortcut(path)
62
+ else install_desktop_entry(path)
63
+ end
64
+ path
65
+ end
66
+
67
+ def uninstall
68
+ path = destination
69
+ return nil unless File.exist?(path)
70
+ FileUtils.rm_rf(path)
71
+ alternative = windows? ? path.sub(/\.lnk\z/, ".cmd") : nil
72
+ FileUtils.rm_f(alternative) if alternative
73
+ path
74
+ end
75
+
76
+ # ---- macOS ---------------------------------------------------------------
77
+
78
+ def install_bundle(bundle)
79
+ FileUtils.rm_rf(bundle)
80
+ FileUtils.mkdir_p(File.join(bundle, "Contents", "MacOS"))
81
+ FileUtils.mkdir_p(File.join(bundle, "Contents", "Resources"))
82
+ File.write(File.join(bundle, "Contents", "Info.plist"), plist)
83
+ runner = File.join(bundle, "Contents", "MacOS", "rcas")
84
+ ruby, script = command
85
+ File.write(runner, "#!/bin/sh\nexec #{quote(ruby)} #{quote(script)} \"$@\"\n")
86
+ File.chmod(0o755, runner)
87
+ icns(File.join(bundle, "Contents", "Resources", "rcas.icns"))
88
+ bundle
89
+ end
90
+
91
+ def plist
92
+ <<~XML
93
+ <?xml version="1.0" encoding="UTF-8"?>
94
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
95
+ <plist version="1.0">
96
+ <dict>
97
+ <key>CFBundleName</key><string>rcas</string>
98
+ <key>CFBundleDisplayName</key><string>rcas</string>
99
+ <key>CFBundleExecutable</key><string>rcas</string>
100
+ <key>CFBundleIdentifier</key><string>#{IDENTIFIER}</string>
101
+ <key>CFBundleIconFile</key><string>rcas</string>
102
+ <key>CFBundlePackageType</key><string>APPL</string>
103
+ <key>CFBundleShortVersionString</key><string>#{RCAS::VERSION}</string>
104
+ <key>CFBundleVersion</key><string>#{RCAS::VERSION}</string>
105
+ <key>NSHighResolutionCapable</key><true/>
106
+ <key>LSMinimumSystemVersion</key><string>10.13</string>
107
+ </dict>
108
+ </plist>
109
+ XML
110
+ end
111
+
112
+ # The logo as an .icns, if macOS's own tools are there to convert it.
113
+ def icns(path)
114
+ logo = App.logo
115
+ sips = Render.which("sips")
116
+ iconutil = Render.which("iconutil")
117
+ return nil unless logo && sips && iconutil
118
+ Dir.mktmpdir("rcas-icon") do |dir|
119
+ iconset = File.join(dir, "rcas.iconset")
120
+ FileUtils.mkdir_p(iconset)
121
+ # Two things iconutil is strict about: the files must really be
122
+ # PNG (sips goes by -s format, not by the name it is given) and
123
+ # each must be exactly square, which -Z and --padToHeightWidth do
124
+ # together without squashing a logo that is wider than it is tall.
125
+ [16, 32, 128, 256, 512].each do |size|
126
+ { "" => size, "@2x" => size * 2 }.each do |suffix, pixels|
127
+ Render.run(sips, "-s", "format", "png", "-Z", pixels.to_s,
128
+ "--padToHeightWidth", pixels.to_s, pixels.to_s, "--padColor", "FFFFFF",
129
+ logo, "--out", File.join(iconset, "icon_#{size}x#{size}#{suffix}.png"))
130
+ end
131
+ end
132
+ Render.run(iconutil, "-c", "icns", iconset, "-o", path)
133
+ end
134
+ path
135
+ rescue Render::Error, SystemCallError
136
+ nil # an application without an icon still runs
137
+ end
138
+
139
+ # ---- Linux ---------------------------------------------------------------
140
+
141
+ def install_desktop_entry(path)
142
+ File.write(path, desktop_entry)
143
+ File.chmod(0o644, path)
144
+ path
145
+ end
146
+
147
+ def desktop_entry
148
+ ruby, script = command
149
+ <<~ENTRY
150
+ [Desktop Entry]
151
+ Type=Application
152
+ Version=1.0
153
+ Name=rcas
154
+ GenericName=Computer Algebra System
155
+ Comment=Exact mathematics: algebra, calculus, plots
156
+ Exec=#{quote(ruby)} #{quote(script)}
157
+ Icon=#{App.logo || 'accessories-calculator'}
158
+ Terminal=false
159
+ Categories=Education;Science;Math;
160
+ Keywords=algebra;calculus;mathematics;cas;
161
+ StartupWMClass=rcas
162
+ ENTRY
163
+ end
164
+
165
+ # ---- Windows -------------------------------------------------------------
166
+
167
+ def install_shortcut(path)
168
+ ruby, script = command
169
+ powershell = Render.which("powershell.exe", "powershell", "pwsh.exe", "pwsh")
170
+ if powershell
171
+ begin
172
+ Render.run(powershell, "-NoProfile", "-NonInteractive", "-Command", shortcut_script(path, ruby, script))
173
+ return path if File.exist?(path)
174
+ rescue Render::Error
175
+ nil # fall through to the .cmd
176
+ end
177
+ end
178
+ batch = path.sub(/\.lnk\z/, ".cmd")
179
+ File.write(batch, "@echo off\r\nstart \"rcas\" /b #{quote(ruby)} #{quote(script)} %*\r\n")
180
+ batch
181
+ end
182
+
183
+ def shortcut_script(path, ruby, script)
184
+ # rubyw runs without a console window; plain ruby is the fallback.
185
+ runner = ruby.sub(/ruby\.exe\z/i, "rubyw.exe")
186
+ runner = ruby unless File.exist?(runner)
187
+ <<~PS.gsub("\n", "; ")
188
+ $s = (New-Object -ComObject WScript.Shell).CreateShortcut('#{path}')
189
+ $s.TargetPath = '#{runner}'
190
+ $s.Arguments = '"#{script}"'
191
+ $s.WorkingDirectory = '#{App.root}'
192
+ $s.IconLocation = '#{App.logo}'
193
+ $s.Description = 'rcas - computer algebra'
194
+ $s.Save()
195
+ PS
196
+ end
197
+
198
+ # ---- helpers -------------------------------------------------------------
199
+
200
+ def quote(word) = word.to_s.match?(/\A[\w.\/:\\-]+\z/) ? word.to_s : %("#{word}")
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,402 @@
1
+ /* The window: a worksheet of In/Out cells. Light and dark are the same
2
+ design with the palette swapped; the whole palette is defined on :root
3
+ so that either media query or [data-theme] can redefine it. */
4
+
5
+ :root {
6
+ color-scheme: light dark;
7
+
8
+ --ground: #fbfbfa;
9
+ --panel: #ffffff;
10
+ --sunken: #f3f3f1;
11
+ --ink: #1c1b19;
12
+ --ink-dim: #6b6a66;
13
+ --ink-faint: #9b9a95;
14
+ --rule: #e4e3df;
15
+ --accent: #7c3a1d;
16
+ --accent-soft: #f0e6e0;
17
+ --result: #1f5c3d;
18
+ --danger: #a32020;
19
+ --link: #2b5fa8;
20
+
21
+ --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
22
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
23
+ --gutter: 20px;
24
+ --label: 4.5rem;
25
+ }
26
+
27
+ @media (prefers-color-scheme: dark) {
28
+ :root:not([data-theme="light"]) {
29
+ --ground: #17171a;
30
+ --panel: #1e1e22;
31
+ --sunken: #26262b;
32
+ --ink: #e8e7e3;
33
+ --ink-dim: #a3a29c;
34
+ --ink-faint: #74736e;
35
+ --rule: #32323a;
36
+ --accent: #e0a37a;
37
+ --accent-soft: #2c2520;
38
+ --result: #7fcfa3;
39
+ --danger: #ef8a8a;
40
+ --link: #8ab4f8;
41
+ }
42
+ }
43
+
44
+ :root[data-theme="dark"] {
45
+ --ground: #17171a;
46
+ --panel: #1e1e22;
47
+ --sunken: #26262b;
48
+ --ink: #e8e7e3;
49
+ --ink-dim: #a3a29c;
50
+ --ink-faint: #74736e;
51
+ --rule: #32323a;
52
+ --accent: #e0a37a;
53
+ --accent-soft: #2c2520;
54
+ --result: #7fcfa3;
55
+ --danger: #ef8a8a;
56
+ --link: #8ab4f8;
57
+ }
58
+
59
+ * { box-sizing: border-box; }
60
+
61
+ html, body {
62
+ height: 100%;
63
+ margin: 0;
64
+ }
65
+
66
+ body {
67
+ display: flex;
68
+ flex-direction: column;
69
+ background: var(--ground);
70
+ color: var(--ink);
71
+ font-family: var(--sans);
72
+ font-size: 14px;
73
+ line-height: 1.55;
74
+ overflow: hidden;
75
+ }
76
+
77
+ /* ---- title bar ---------------------------------------------------------- */
78
+
79
+ .titlebar {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 10px;
83
+ flex: none;
84
+ padding: 10px var(--gutter);
85
+ padding-top: calc(10px + env(safe-area-inset-top, 0px));
86
+ border-bottom: 1px solid var(--rule);
87
+ background: var(--panel);
88
+ user-select: none;
89
+ }
90
+
91
+ .titlebar .logo {
92
+ width: 22px;
93
+ height: 22px;
94
+ border-radius: 5px;
95
+ object-fit: cover;
96
+ }
97
+
98
+ .titlebar h1 {
99
+ margin: 0;
100
+ font-size: 15px;
101
+ font-weight: 600;
102
+ letter-spacing: 0.01em;
103
+ }
104
+
105
+ .titlebar .version { color: var(--ink-faint); font-size: 12px; }
106
+ .titlebar .spacer { flex: 1; }
107
+
108
+ .modes {
109
+ display: flex;
110
+ gap: 2px;
111
+ padding: 2px;
112
+ border-radius: 7px;
113
+ background: var(--sunken);
114
+ }
115
+
116
+ .modes button {
117
+ padding: 3px 9px;
118
+ border: 0;
119
+ border-radius: 5px;
120
+ background: transparent;
121
+ color: var(--ink-dim);
122
+ font: inherit;
123
+ font-size: 12px;
124
+ cursor: pointer;
125
+ }
126
+
127
+ .modes button[aria-pressed="true"] {
128
+ background: var(--panel);
129
+ color: var(--ink);
130
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.12);
131
+ }
132
+
133
+ button.ghost {
134
+ width: 26px;
135
+ height: 26px;
136
+ border: 1px solid var(--rule);
137
+ border-radius: 50%;
138
+ background: transparent;
139
+ color: var(--ink-dim);
140
+ font: inherit;
141
+ cursor: pointer;
142
+ }
143
+
144
+ button.ghost:hover, .modes button:hover { color: var(--ink); }
145
+
146
+ /* ---- worksheet ---------------------------------------------------------- */
147
+
148
+ .worksheet {
149
+ flex: 1;
150
+ overflow-y: auto;
151
+ overscroll-behavior: contain;
152
+ padding: 18px var(--gutter) 8px;
153
+ outline: none;
154
+ }
155
+
156
+ .cell {
157
+ margin: 0 0 18px;
158
+ animation: rise 0.18s ease-out;
159
+ }
160
+
161
+ @keyframes rise {
162
+ from { opacity: 0; transform: translateY(3px); }
163
+ to { opacity: 1; transform: none; }
164
+ }
165
+
166
+ @media (prefers-reduced-motion: reduce) {
167
+ .cell { animation: none; }
168
+ }
169
+
170
+ .line {
171
+ display: flex;
172
+ align-items: flex-start;
173
+ gap: 10px;
174
+ }
175
+
176
+ .line + .line { margin-top: 3px; }
177
+
178
+ .tag {
179
+ flex: none;
180
+ width: var(--label);
181
+ text-align: right;
182
+ font-family: var(--mono);
183
+ font-size: 11.5px;
184
+ line-height: 1.55;
185
+ color: var(--ink-faint);
186
+ user-select: none;
187
+ }
188
+
189
+ .line.in .tag { color: var(--accent); }
190
+
191
+ .body {
192
+ flex: 1;
193
+ min-width: 0;
194
+ }
195
+
196
+ .line.in .body {
197
+ font-family: var(--mono);
198
+ font-size: 13px;
199
+ white-space: pre-wrap;
200
+ word-break: break-word;
201
+ cursor: text;
202
+ }
203
+
204
+ .line.in .body:hover { color: var(--accent); }
205
+
206
+ pre.text {
207
+ margin: 0;
208
+ font-family: var(--mono);
209
+ font-size: 13px;
210
+ white-space: pre;
211
+ overflow-x: auto;
212
+ color: var(--result);
213
+ }
214
+
215
+ pre.stdout {
216
+ margin: 0 0 4px;
217
+ font-family: var(--mono);
218
+ font-size: 13px;
219
+ white-space: pre;
220
+ overflow-x: auto;
221
+ color: var(--ink-dim);
222
+ }
223
+
224
+ .tex {
225
+ margin-top: 6px;
226
+ overflow-x: auto;
227
+ overflow-y: hidden;
228
+ padding: 2px 0;
229
+ }
230
+
231
+ .tex .katex { font-size: 1.12em; }
232
+ /* A worksheet reads down the left edge; display maths must not centre. */
233
+ .tex .katex-display { margin: 0; text-align: left; }
234
+ .tex .katex-display > .katex { text-align: left; }
235
+
236
+ .source {
237
+ display: block;
238
+ margin-top: 6px;
239
+ padding: 6px 9px;
240
+ border-radius: 6px;
241
+ background: var(--sunken);
242
+ font-family: var(--mono);
243
+ font-size: 12px;
244
+ color: var(--ink-dim);
245
+ white-space: pre-wrap;
246
+ word-break: break-all;
247
+ }
248
+
249
+ .plot { margin-top: 6px; }
250
+ .plot svg { width: 100%; max-width: 560px; height: auto; border-radius: 8px; }
251
+
252
+ .error { color: var(--danger); }
253
+ .hint { margin-top: 3px; color: var(--ink-dim); font-family: var(--mono); font-size: 12px; }
254
+ .hint span { display: block; }
255
+ .hint span:first-child { color: var(--link); }
256
+ .note { color: var(--ink-faint); font-size: 12px; }
257
+ .warning { color: var(--accent); font-family: var(--mono); font-size: 12px; margin-bottom: 3px; }
258
+ .took { color: var(--ink-faint); font-size: 11px; font-family: var(--mono); }
259
+
260
+ /* ---- tables: /help, /vars, /assumptions ---------------------------------- */
261
+
262
+ table.rows {
263
+ width: 100%;
264
+ border-collapse: collapse;
265
+ font-size: 13px;
266
+ }
267
+
268
+ table.rows td {
269
+ padding: 3px 12px 3px 0;
270
+ vertical-align: baseline;
271
+ }
272
+
273
+ table.rows td.name {
274
+ width: 1%;
275
+ white-space: nowrap;
276
+ font-family: var(--mono);
277
+ font-size: 12.5px;
278
+ color: var(--accent);
279
+ }
280
+
281
+ table.rows td.value { font-family: var(--mono); font-size: 12.5px; }
282
+
283
+ .doc h2 {
284
+ margin: 0 0 4px;
285
+ font-family: var(--mono);
286
+ font-size: 13px;
287
+ font-weight: 600;
288
+ color: var(--accent);
289
+ }
290
+
291
+ .doc p { margin: 0 0 6px; max-width: 62ch; }
292
+ .doc .field { margin-bottom: 6px; max-width: 72ch; }
293
+ .doc .field > b { color: var(--link); font-weight: 600; }
294
+ .doc a { color: var(--link); }
295
+ .doc ul { margin: 0; padding-left: 18px; }
296
+
297
+ /* ---- greeting ----------------------------------------------------------- */
298
+
299
+ .greeting { max-width: 64ch; color: var(--ink-dim); }
300
+ .greeting p { margin: 0 0 10px; }
301
+ .greeting .dim { color: var(--ink-faint); font-size: 12.5px; }
302
+ .greeting code { font-family: var(--mono); }
303
+
304
+ .examples {
305
+ list-style: none;
306
+ margin: 0 0 14px;
307
+ padding: 0;
308
+ display: flex;
309
+ flex-wrap: wrap;
310
+ gap: 6px;
311
+ }
312
+
313
+ .examples button {
314
+ padding: 4px 10px;
315
+ border: 1px solid var(--rule);
316
+ border-radius: 999px;
317
+ background: var(--panel);
318
+ color: var(--ink-dim);
319
+ font-family: var(--mono);
320
+ font-size: 12px;
321
+ cursor: pointer;
322
+ }
323
+
324
+ .examples button:hover {
325
+ border-color: var(--accent);
326
+ color: var(--accent);
327
+ background: var(--accent-soft);
328
+ }
329
+
330
+ /* ---- composer ----------------------------------------------------------- */
331
+
332
+ .composer {
333
+ flex: none;
334
+ border-top: 1px solid var(--rule);
335
+ background: var(--panel);
336
+ padding: 10px var(--gutter);
337
+ padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
338
+ }
339
+
340
+ .prompt-row {
341
+ display: flex;
342
+ align-items: baseline;
343
+ gap: 10px;
344
+ }
345
+
346
+ .prompt {
347
+ flex: none;
348
+ width: var(--label);
349
+ text-align: right;
350
+ font-family: var(--mono);
351
+ font-size: 11.5px;
352
+ color: var(--accent);
353
+ user-select: none;
354
+ }
355
+
356
+ #input {
357
+ flex: 1;
358
+ min-width: 0;
359
+ max-height: 9.5em;
360
+ border: 0;
361
+ padding: 0;
362
+ resize: none;
363
+ background: transparent;
364
+ color: var(--ink);
365
+ font-family: var(--mono);
366
+ font-size: 13.5px;
367
+ line-height: 1.5;
368
+ outline: none;
369
+ overflow-y: auto;
370
+ }
371
+
372
+ #input::placeholder { color: var(--ink-faint); }
373
+
374
+ .completions {
375
+ display: flex;
376
+ flex-wrap: wrap;
377
+ gap: 4px;
378
+ margin: 0 0 8px calc(var(--label) + 10px);
379
+ }
380
+
381
+ .completions button {
382
+ padding: 2px 8px;
383
+ border: 1px solid var(--rule);
384
+ border-radius: 5px;
385
+ background: var(--sunken);
386
+ color: var(--ink-dim);
387
+ font-family: var(--mono);
388
+ font-size: 12px;
389
+ cursor: pointer;
390
+ }
391
+
392
+ .completions button[aria-selected="true"] {
393
+ border-color: var(--accent);
394
+ background: var(--accent-soft);
395
+ color: var(--accent);
396
+ }
397
+
398
+ /* The window is narrow before it is anything else. */
399
+ @media (max-width: 560px) {
400
+ :root { --gutter: 12px; --label: 3.2rem; }
401
+ .line { gap: 7px; }
402
+ }