@ivanbard/dd2l 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/LICENSE +21 -0
- package/README.md +83 -3
- package/THIRD_PARTY_NOTICES.md +50 -0
- package/bin/dd2l.mjs +7 -0
- package/docs/README.md +130 -0
- package/package.json +55 -6
- package/src/app-runner.mjs +35 -0
- package/src/cli.mjs +120 -0
- package/src/client.mjs +137 -0
- package/src/connector.mjs +345 -0
- package/src/discovery.mjs +45 -0
- package/src/documents.mjs +154 -0
- package/src/links.mjs +99 -0
- package/src/llm.mjs +206 -0
- package/src/media.mjs +54 -0
- package/src/ocr.mjs +24 -0
- package/src/office.mjs +61 -0
- package/src/pdf-worker.mjs +26 -0
- package/src/session.mjs +142 -0
- package/src/store.mjs +106 -0
- package/src/syllabus.mjs +191 -0
- package/src/ui-data.mjs +101 -0
- package/src/ui-demo.mjs +20 -0
- package/src/ui-server.mjs +125 -0
- package/src/windows-ocr.ps1 +32 -0
- package/ui/answer-renderer.js +88 -0
- package/ui/answer-view.js +47 -0
- package/ui/app.js +302 -0
- package/ui/assets/LUCIDE-LICENSE.txt +15 -0
- package/ui/assets/avatar.svg +3 -0
- package/ui/assets/chevron.svg +5 -0
- package/ui/assets/courses.svg +14 -0
- package/ui/assets/logo.svg +13 -0
- package/ui/assets/new-chat.svg +14 -0
- package/ui/assets/search.svg +14 -0
- package/ui/assets/send.svg +8 -0
- package/ui/assets/settings.svg +14 -0
- package/ui/assets/widget-announcements.svg +4 -0
- package/ui/assets/widget-shortcuts.svg +5 -0
- package/ui/index.html +73 -0
- package/ui/loading.js +33 -0
- package/ui/style.css +226 -0
- package/ui/widget-layout.js +39 -0
- package/ui/widgets.js +252 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g id="logo">
|
|
3
|
+
<circle id="Ellipse 6" cx="4.33333" cy="4.33333" r="4.33333" fill="white" fill-opacity="0.1"/>
|
|
4
|
+
<circle id="Ellipse 7" cx="15" cy="4.33333" r="4.33333" fill="white"/>
|
|
5
|
+
<circle id="Ellipse 8" cx="25.6667" cy="4.33333" r="4.33333" fill="white" fill-opacity="0.1"/>
|
|
6
|
+
<circle id="Ellipse 3" cx="4.33333" cy="15" r="4.33333" fill="white"/>
|
|
7
|
+
<circle id="Ellipse 4" cx="15" cy="15" r="4.33333" fill="white"/>
|
|
8
|
+
<circle id="Ellipse 5" cx="25.6667" cy="15" r="4.33333" fill="white"/>
|
|
9
|
+
<circle id="Ellipse 9" cx="4.33333" cy="25.6667" r="4.33333" fill="white" fill-opacity="0.1"/>
|
|
10
|
+
<circle id="Ellipse 10" cx="15" cy="25.6667" r="4.33333" fill="white"/>
|
|
11
|
+
<circle id="Ellipse 11" cx="25.6667" cy="25.6667" r="4.33333" fill="white" fill-opacity="0.1"/>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="#bababa"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
13
|
+
<path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="#bababa"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<circle cx="11" cy="11" r="8" />
|
|
13
|
+
<path d="m21 21-4.3-4.3" />
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g id="Send-Greyed">
|
|
3
|
+
<circle id="Ellipse 1" cx="16" cy="16" r="16" fill="#525252"/>
|
|
4
|
+
<g id="Arrow up">
|
|
5
|
+
<path id="Icon" d="M16 21.6V10.4M21.6 16L16 10.4L10.4 16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="#353535"/>
|
|
6
|
+
</g>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="#bababa"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
|
|
13
|
+
<circle cx="12" cy="12" r="3" />
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" fill="none">
|
|
2
|
+
<defs><pattern id="dots" width="4" height="4" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r=".9" fill="#c2c3ae"/></pattern></defs>
|
|
3
|
+
<g fill="url(#dots)"><circle cx="160" cy="72" r="47"/><circle cx="244" cy="133" r="47"/><circle cx="212" cy="231" r="47"/><circle cx="108" cy="231" r="47"/><circle cx="76" cy="133" r="47"/></g>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" fill="none">
|
|
2
|
+
<defs><pattern id="dots" width="4" height="4" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r=".9" fill="#c9c0ad"/></pattern></defs>
|
|
3
|
+
<path d="M40 99h81L39 181l35 35 82-82v81h49V50H40z" fill="url(#dots)"/>
|
|
4
|
+
<path d="M143 154h65l-66 66 28 28 66-66v65h39V115H143z" fill="url(#dots)" opacity=".4"/>
|
|
5
|
+
</svg>
|
package/ui/index.html
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="color-scheme" content="dark">
|
|
7
|
+
<title>dd2l · Your coursework</title>
|
|
8
|
+
<link rel="icon" href="/assets/logo.svg" type="image/svg+xml">
|
|
9
|
+
<link rel="stylesheet" href="/style.css">
|
|
10
|
+
<script type="module" src="/app.js"></script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<a class="skip-link" href="#prompt">Skip to search</a>
|
|
14
|
+
<aside class="sidebar" aria-label="Main navigation">
|
|
15
|
+
<a class="brand" href="/" aria-label="dd2l home"><img src="/assets/logo.svg" alt="" width="30" height="30"></a>
|
|
16
|
+
<nav>
|
|
17
|
+
<button class="icon-button" id="new-chat" type="button" title="New search" aria-label="New search"><img src="/assets/new-chat.svg" alt="" width="16" height="16"></button>
|
|
18
|
+
<button class="icon-button" id="search-nav" type="button" title="Search coursework" aria-label="Search coursework"><img src="/assets/search.svg" alt="" width="16" height="16"></button>
|
|
19
|
+
<button class="icon-button" id="courses-nav" type="button" title="Courses" aria-label="Courses"><img src="/assets/courses.svg" alt="" width="16" height="16"></button>
|
|
20
|
+
</nav>
|
|
21
|
+
<div class="utilities">
|
|
22
|
+
<button class="icon-button" id="settings-nav" type="button" title="Connection status" aria-label="Connection status"><img src="/assets/settings.svg" alt="" width="16" height="16"></button>
|
|
23
|
+
<button class="profile" id="profile" type="button" title="Local profile" aria-label="Local profile"><img src="/assets/avatar.svg" alt="" width="40" height="40"><span>U</span></button>
|
|
24
|
+
</div>
|
|
25
|
+
</aside>
|
|
26
|
+
<span id="demo-badge" class="demo-badge" hidden>Demo · sample coursework</span>
|
|
27
|
+
<main id="workspace" class="workspace">
|
|
28
|
+
<section class="home" aria-labelledby="heading">
|
|
29
|
+
<h1 id="heading">What are we looking for today?</h1>
|
|
30
|
+
<form id="search-form" class="composer" role="search">
|
|
31
|
+
<div class="prompt-field">
|
|
32
|
+
<label class="sr-only" for="prompt">Search your cached coursework</label>
|
|
33
|
+
<input id="prompt" name="q" type="search" maxlength="500" autocomplete="off" aria-describedby="search-help">
|
|
34
|
+
<button id="send" type="submit" disabled aria-label="Search"><img src="/assets/send.svg" alt="" width="32" height="32"><span id="busy" hidden></span></button>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="composer-footer">
|
|
37
|
+
<label class="sr-only" for="course">Course</label>
|
|
38
|
+
<select id="course" name="course"><option value="">All courses</option></select>
|
|
39
|
+
<span id="course-loading" class="loading-slot"></span>
|
|
40
|
+
<label class="sr-only" for="model">Search mode or model</label>
|
|
41
|
+
<select id="model" name="model" aria-describedby="search-help"><option value="">Local search</option></select>
|
|
42
|
+
<span id="model-loading" class="loading-slot"></span>
|
|
43
|
+
</div>
|
|
44
|
+
<span id="search-help" class="sr-only">Find source excerpts by keyword, or connect ChatGPT in Settings to ask a question.</span>
|
|
45
|
+
</form>
|
|
46
|
+
<p id="model-sharing" class="model-sharing" hidden>Sending with this model shares your question and matching coursework with OpenAI.</p>
|
|
47
|
+
<p id="notice" class="notice" role="status" hidden></p>
|
|
48
|
+
</section>
|
|
49
|
+
<section id="home-widgets" class="home-widgets" aria-labelledby="widgets-heading">
|
|
50
|
+
<div class="widgets-heading"><div><p class="eyebrow">YOUR SPACE</p><h2 id="widgets-heading">At a glance <span id="widget-loading" class="loading-slot"></span></h2></div>
|
|
51
|
+
<div class="widget-actions"><button id="refresh-widgets" type="button">Refresh cache view</button><button id="add-widget" type="button" hidden>+ Add widget</button><button id="customize-widgets" type="button" aria-pressed="false">Customize</button></div>
|
|
52
|
+
</div>
|
|
53
|
+
<p id="widget-help" class="widget-help">From your cached coursework.</p>
|
|
54
|
+
<div id="widget-grid" class="widget-grid"></div>
|
|
55
|
+
<p id="widget-status" class="widget-status" role="status"></p>
|
|
56
|
+
</section>
|
|
57
|
+
<section id="ai-answer" class="results" aria-label="AI answer" hidden>
|
|
58
|
+
<h2 id="ai-answer-title" tabindex="-1">Your answer</h2><p id="answer-text"></p>
|
|
59
|
+
<div id="answer-view" class="answer-view" hidden></div>
|
|
60
|
+
<h3 class="answer-sources-heading">Sources</h3><div id="answer-sources"></div>
|
|
61
|
+
</section>
|
|
62
|
+
<section id="results" class="results" aria-label="Coursework results" hidden>
|
|
63
|
+
<div class="results-heading"><h2 id="results-title" tabindex="-1">Sources</h2><span id="result-count"></span></div>
|
|
64
|
+
<p class="retrieval-note">Matching excerpts from your local inventory. These are source materials, not an AI-generated answer.</p>
|
|
65
|
+
<div id="result-list"></div>
|
|
66
|
+
</section>
|
|
67
|
+
</main>
|
|
68
|
+
<dialog id="panel" aria-labelledby="panel-title">
|
|
69
|
+
<header><h2 id="panel-title"></h2><span id="panel-loading" class="loading-slot"></span><button id="close-panel" type="button" aria-label="Close panel">×</button></header>
|
|
70
|
+
<div id="panel-body"></div>
|
|
71
|
+
</dialog>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
package/ui/loading.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Square 12 / Origin Wave, adapted for dd2l from zzzzshawn/matrix. See THIRD_PARTY_NOTICES.md.
|
|
2
|
+
const active = new WeakMap();
|
|
3
|
+
|
|
4
|
+
export function startLoading(target, label = 'Loading') {
|
|
5
|
+
let state = active.get(target);
|
|
6
|
+
if (!state) {
|
|
7
|
+
const loader = document.createElement('span');
|
|
8
|
+
loader.className = 'dot-loader';
|
|
9
|
+
loader.setAttribute('role', 'status');
|
|
10
|
+
loader.setAttribute('aria-label', label);
|
|
11
|
+
for (let row = 0; row < 5; row++) for (let col = 0; col < 5; col++) {
|
|
12
|
+
const dot = document.createElement('span');
|
|
13
|
+
dot.setAttribute('aria-hidden', 'true');
|
|
14
|
+
dot.style.setProperty('--ring', Math.abs(row - 1) + Math.abs(col - 1));
|
|
15
|
+
loader.append(dot);
|
|
16
|
+
}
|
|
17
|
+
state = { loader, count: 0, previousBusy: target.getAttribute('aria-busy') };
|
|
18
|
+
active.set(target, state);
|
|
19
|
+
target.append(loader);
|
|
20
|
+
target.setAttribute('aria-busy', 'true');
|
|
21
|
+
}
|
|
22
|
+
state.count++;
|
|
23
|
+
let finished = false;
|
|
24
|
+
return () => {
|
|
25
|
+
if (finished) return;
|
|
26
|
+
finished = true;
|
|
27
|
+
if (--state.count) return;
|
|
28
|
+
state.loader.remove();
|
|
29
|
+
if (state.previousBusy === null) target.removeAttribute('aria-busy');
|
|
30
|
+
else target.setAttribute('aria-busy', state.previousBusy);
|
|
31
|
+
active.delete(target);
|
|
32
|
+
};
|
|
33
|
+
}
|
package/ui/style.css
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
:root { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', sans-serif; color: #fff; background: #222; color-scheme: dark; font-synthesis: none; }
|
|
2
|
+
* { box-sizing: border-box; }
|
|
3
|
+
body { margin: 0; min-width: 320px; }
|
|
4
|
+
button, input, select { font: inherit; }
|
|
5
|
+
button, a, select { -webkit-tap-highlight-color: transparent; }
|
|
6
|
+
button { cursor: pointer; }
|
|
7
|
+
button:disabled { cursor: default; }
|
|
8
|
+
button, select { color: inherit; }
|
|
9
|
+
button:focus-visible, a:focus-visible, select:focus-visible, input:focus-visible, h2:focus-visible { outline: 2px solid #75c9e2; outline-offset: 4px; }
|
|
10
|
+
[hidden] { display: none !important; }
|
|
11
|
+
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
12
|
+
.skip-link { position: fixed; top: -80px; left: 75px; z-index: 5; background: #333; color: white; padding: 12px; }
|
|
13
|
+
.skip-link:focus { top: 12px; }
|
|
14
|
+
.sidebar { position: fixed; inset: 0 auto 0 0; width: 60px; height: 100dvh; background: #1a1a1a; border-right: 1px solid #5e5e5e66; display: flex; flex-direction: column; align-items: center; z-index: 2; }
|
|
15
|
+
.brand { display: grid; place-items: center; width: 44px; height: 44px; margin-top: 12px; }
|
|
16
|
+
.brand img, .icon-button img { display: block; }
|
|
17
|
+
nav { margin-top: 20px; display: grid; gap: 8px; }
|
|
18
|
+
.icon-button { width: 44px; height: 44px; padding: 14px; border: 0; border-radius: 10px; background: transparent; transition: background .15s ease; }
|
|
19
|
+
.icon-button:hover, .icon-button[aria-current] { background: #ffffff0c; }
|
|
20
|
+
.utilities { margin-top: auto; display: flex; align-items: center; flex-direction: column; gap: 12px; padding-bottom: 9px; }
|
|
21
|
+
.profile { display: grid; place-items: center; position: relative; width: 40px; height: 40px; padding: 0; border: 0; background: transparent; border-radius: 50%; }
|
|
22
|
+
.profile img, .profile span { grid-area: 1 / 1; }
|
|
23
|
+
.profile span { font-size: 20px; }
|
|
24
|
+
.workspace { padding: 88px 48px 64px 108px; }
|
|
25
|
+
.home { width: min(560px, 100%); margin: 0 auto; }
|
|
26
|
+
h1 { font-size: 24px; line-height: 29px; font-weight: 400; text-align: center; margin: 0 0 6px; letter-spacing: -.4px; }
|
|
27
|
+
.composer { height: 128px; padding: 20px; border-radius: 42px; background: linear-gradient(137deg, #313131 -10%, #1a1a1a 86%); box-shadow: 5px 7px 13.5px 8px #0004; }
|
|
28
|
+
.prompt-field { height: 44px; border-radius: 22px; background: #2f2f2f; border: 1px solid #5e5e5e66; display: flex; align-items: center; padding: 5px 7px 5px 15px; transition: border-color .15s ease; }
|
|
29
|
+
.prompt-field:focus-within { border-color: #818181; }
|
|
30
|
+
input { min-width: 0; width: 100%; border: 0; outline: none; color: #fff; background: transparent; font-size: 16px; }
|
|
31
|
+
input:focus-visible { outline: none; }
|
|
32
|
+
input::-webkit-search-cancel-button { display: none; }
|
|
33
|
+
#send { display: grid; place-items: center; flex: 0 0 32px; height: 32px; border: 0; padding: 0; background: transparent; border-radius: 50%; transition: filter .15s ease, transform .15s ease; }
|
|
34
|
+
#send img { display: block; }
|
|
35
|
+
.composer[aria-busy=true] #send img { display: none; }
|
|
36
|
+
#send:not(:disabled) { filter: brightness(2); }
|
|
37
|
+
#send:not(:disabled):hover { transform: translateY(-1px); filter: brightness(2.6); }
|
|
38
|
+
.composer-footer { position: relative; display: flex; align-items: center; gap: 4px; margin-top: 12px; height: 32px; }
|
|
39
|
+
.composer-footer select { min-width: 0; max-width: 55%; }
|
|
40
|
+
.composer-footer #model { margin-left: auto; max-width: 45%; }
|
|
41
|
+
.loading-slot:empty { display: none; }
|
|
42
|
+
.loading-slot { display: inline-flex; vertical-align: middle; flex-shrink: 0; }
|
|
43
|
+
.dot-loader { display: inline-grid; grid-template-columns: repeat(5, 3px); grid-template-rows: repeat(5, 3px); gap: 2.25px; width: 24px; height: 24px; flex: 0 0 24px; vertical-align: middle; color: inherit; }
|
|
44
|
+
.dot-loader > span { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .24; animation: dot-origin-wave 1500ms ease-in-out infinite; animation-delay: calc(var(--ring) * 240ms); will-change: opacity; }
|
|
45
|
+
@keyframes dot-origin-wave { 0%, 100% { opacity: .1; } 34% { opacity: 1; } 60% { opacity: .24; } }
|
|
46
|
+
#panel-loading { margin-left: auto; margin-right: 12px; }
|
|
47
|
+
.account-action[aria-busy=true] { display: flex; align-items: center; gap: 12px; }
|
|
48
|
+
select { appearance: none; field-sizing: content; max-width: min(350px, 85%); padding: 8px 30px 8px 12px; border: 0; border-radius: 8px; font-size: 13px; line-height: 16px; background: transparent url('/assets/chevron.svg') right 10px center / 14px 14px no-repeat; color: #bababa; cursor: pointer; text-overflow: ellipsis; }
|
|
49
|
+
select:hover { background-color: #ffffff08; }
|
|
50
|
+
option { background: #292929; color: white; }
|
|
51
|
+
#busy { color: #bababa; font-size: 12px; }
|
|
52
|
+
.notice { color: #bababa; font-size: 13px; line-height: 1.6; margin: 24px 12px 0; text-align: center; }
|
|
53
|
+
.demo-badge { position: fixed; right: 20px; top: 18px; color: #aaa; font-size: 12px; }
|
|
54
|
+
.workspace.has-results { padding-top: 64px; }
|
|
55
|
+
.has-results .home { width: min(680px, 100%); transform: none; }
|
|
56
|
+
.has-results h1 { font-size: 20px; margin-bottom: 18px; }
|
|
57
|
+
.results { max-width: 680px; margin: 44px auto 0; animation: reveal .18s ease-out; }
|
|
58
|
+
.results-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
|
|
59
|
+
h2 { font-size: 18px; font-weight: 500; margin: 0; }
|
|
60
|
+
#result-count, .retrieval-note, .source-meta { font-size: 12px; color: #aaa; line-height: 1.6; }
|
|
61
|
+
.retrieval-note { margin: 12px 0 22px; }
|
|
62
|
+
.source { padding: 22px 0; border-top: 1px solid #ffffff12; }
|
|
63
|
+
.source h3 { font-size: 16px; font-weight: 500; margin: 8px 0 12px; }
|
|
64
|
+
.source p { font-size: 14px; color: #d0d0d0; line-height: 1.7; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0 0 14px; }
|
|
65
|
+
.source a { color: #a7d7e5; text-decoration: none; font-size: 12px; }
|
|
66
|
+
.source a:hover { text-decoration: underline; }
|
|
67
|
+
.stale { color: #e6c58a; }
|
|
68
|
+
.empty { padding: 24px 0; color: #bababa; line-height: 1.7; font-size: 14px; }
|
|
69
|
+
dialog { width: min(460px, calc(100% - 40px)); max-height: 80dvh; padding: 24px; color: #eee; background: #262626; border: 1px solid #484848; border-radius: 20px; box-shadow: 0 20px 80px #0008; }
|
|
70
|
+
dialog::backdrop { background: #0007; }
|
|
71
|
+
dialog header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
|
|
72
|
+
#close-panel { width: 32px; height: 32px; background: transparent; border: 0; border-radius: 8px; color: #bbb; font-size: 24px; }
|
|
73
|
+
#close-panel:hover { background: #ffffff0c; }
|
|
74
|
+
#panel-body { color: #bbb; font-size: 14px; line-height: 1.7; }
|
|
75
|
+
#panel-body p { margin: 12px 0; }
|
|
76
|
+
.course-item { display: block; width: 100%; text-align: left; background: transparent; border: 0; border-top: 1px solid #ffffff12; padding: 16px 4px; color: #eee; line-height: 1.6; }
|
|
77
|
+
.course-item:hover { background: #ffffff06; }
|
|
78
|
+
.course-item small { display: block; color: #999; }
|
|
79
|
+
code { background: #1a1a1a; border-radius: 4px; padding: 3px 6px; font-size: 12px; }
|
|
80
|
+
.model-sharing { color: #aaa; font-size: 11px; line-height: 1.5; margin: 18px 12px 0; }
|
|
81
|
+
#panel-body a { color: #a7d7e5; }
|
|
82
|
+
.account-action { border: 1px solid #555; background: #333; color: #eee; border-radius: 8px; padding: 10px 14px; }
|
|
83
|
+
.account-action { display: block; margin: 12px 0; }
|
|
84
|
+
#answer-text { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.7; color: #ddd; }
|
|
85
|
+
.auth-page { min-height: 100svh; display: grid; place-items: center; padding: 40px 24px; }
|
|
86
|
+
.auth-content { width: min(480px, 100%); }
|
|
87
|
+
.auth-brand { display: inline-flex; align-items: center; gap: 12px; color: #eee; text-decoration: none; font-size: 20px; margin-bottom: 64px; }
|
|
88
|
+
.auth-brand img { display: block; }
|
|
89
|
+
.auth-status { color: #a7d7e5; font-size: 13px; margin: 0 0 16px; }
|
|
90
|
+
.auth-content h1 { text-align: left; font-size: 32px; line-height: 1.2; letter-spacing: -.6px; margin: 0; }
|
|
91
|
+
.auth-message { color: #bbb; font-size: 15px; line-height: 1.7; margin: 20px 0 32px; }
|
|
92
|
+
.auth-return { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 16px 20px; border: 1px solid #555; border-radius: 12px; background: #303030; color: #eee; text-decoration: none; }
|
|
93
|
+
.auth-return:hover { background: #383838; border-color: #777; }
|
|
94
|
+
.auth-usage { display: inline-block; margin-top: 24px; color: #aaa; font-size: 13px; text-underline-offset: 4px; }
|
|
95
|
+
@keyframes reveal { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
|
96
|
+
.home-widgets { max-width: 1120px; margin: 64px auto 0; }
|
|
97
|
+
.has-results .home-widgets { display: none; }
|
|
98
|
+
.widgets-heading { display: flex; justify-content: space-between; align-items: center; gap: 20px; }
|
|
99
|
+
.eyebrow { color: #999; font: 10px / 1.4 ui-monospace, 'Cascadia Mono', monospace; letter-spacing: 1px; margin: 0 0 8px; }
|
|
100
|
+
#widgets-heading { font-size: 25px; font-weight: 400; letter-spacing: -.5px; }
|
|
101
|
+
.widget-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
102
|
+
.widget-actions button, .widget-tools button, .pin-shortcut, .shortcut-row > button:not(.shortcut-link) { background: #ffffff08; border: 1px solid #ffffff1c; border-radius: 8px; color: #ddd; padding: 9px 12px; font-size: 12px; }
|
|
103
|
+
.widget-actions button:hover, .widget-tools button:hover, .pin-shortcut:hover { background: #ffffff14; }
|
|
104
|
+
#customize-widgets[aria-pressed=true] { background: #a7d7e5; color: #17252a; }
|
|
105
|
+
.widget-help, .widget-status { font-size: 12px; color: #aaa; line-height: 1.6; }
|
|
106
|
+
.widget-help { margin: 16px 0 24px; }
|
|
107
|
+
.widget-status { min-height: 20px; }
|
|
108
|
+
.widget-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: 160px; gap: 16px; }
|
|
109
|
+
.widget { container-type: inline-size; position: relative; isolation: isolate; min-width: 0; display: flex; flex-direction: column; border: 1px solid #ffffff12; border-radius: 7px; background: #171819; padding: 18px 22px; overflow: hidden; box-shadow: inset 0 1px #ffffff0c, 0 2px 3px #0003, 0 12px 24px -12px #0009; transition: border-color .2s, box-shadow .2s, translate .2s; animation: reveal .25s ease-out; }
|
|
110
|
+
.widget::before { content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: .22; background: repeating-linear-gradient(0deg, transparent 0 2px, #ffffff0b 2px 3px), repeating-linear-gradient(91deg, transparent 0 3px, #0004 3px 4px); }
|
|
111
|
+
.widget:hover, .widget:focus-within { border-color: #ffffff30; box-shadow: inset 0 1px #ffffff12, 0 3px 5px #0004, 0 18px 32px -12px #000a; }
|
|
112
|
+
.widget:not(.widget-preview):hover { translate: 0 -2px; }
|
|
113
|
+
.editing .widget:not(.widget-preview):hover { translate: none; }
|
|
114
|
+
.widget-edition { position: relative; z-index: 1; display: flex; justify-content: space-between; gap: 10px; flex-shrink: 0; margin-bottom: 12px; color: #a3a5a5; font: 9px / 1 ui-monospace, 'Cascadia Mono', monospace; letter-spacing: 1.2px; }
|
|
115
|
+
.widget-edition span:last-child { letter-spacing: -.5px; font-weight: 700; }
|
|
116
|
+
.widget-mark { position: absolute; z-index: -1; pointer-events: none; right: -16px; top: 6%; width: 52%; height: 90%; opacity: .5; background-position: center; background-size: contain; background-repeat: no-repeat; }
|
|
117
|
+
.widget-announcements .widget-mark { background-image: url('/assets/widget-announcements.svg'); }
|
|
118
|
+
.widget-shortcuts .widget-mark { background-image: url('/assets/widget-shortcuts.svg'); right: -32px; }
|
|
119
|
+
.widget-deadlines .widget-mark { display: flex; flex-direction: column; align-items: center; justify-content: center; color: #b1d9e8; opacity: .7; transform: rotate(-7deg); }
|
|
120
|
+
.widget-deadlines .widget-mark span { font-size: clamp(72px, 28cqi, 180px); font-weight: 850; letter-spacing: -.09em; line-height: .78; background: radial-gradient(circle, #b1d9e8 1px, transparent 1.2px) 0 0 / 3px 3px; background-clip: text; color: transparent; }
|
|
121
|
+
.widget-deadlines .widget-mark small { margin-top: 12px; padding-left: 12px; font: 10px / 1 ui-monospace, monospace; letter-spacing: 7px; }
|
|
122
|
+
.widget-header { position: relative; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: space-between; margin-top: auto; margin-bottom: 9px; flex-shrink: 0; }
|
|
123
|
+
.widget h3 { color: #dededb; font-size: clamp(17px, 4.4cqi, 25px); line-height: 1.05; letter-spacing: -.8px; font-weight: 750; margin: 0; }
|
|
124
|
+
.widget-body { position: relative; width: 72%; overflow: auto; min-height: 0; scrollbar-width: thin; scrollbar-color: #555 transparent; }
|
|
125
|
+
.widget-item { display: block; text-align: left; width: 100%; background: none; border: 0; border-top: 1px solid #ffffff12; padding: 12px 0; color: #eee; }
|
|
126
|
+
.widget-item:first-child { border-top: 0; padding-top: 2px; }
|
|
127
|
+
.widget-item:hover strong { color: #a7d7e5; }
|
|
128
|
+
.widget-item small, .widget-item strong, .widget-item span { display: block; overflow-wrap: anywhere; }
|
|
129
|
+
.widget-item small { font: 10px / 1.5 ui-monospace, 'Cascadia Mono', monospace; color: #b1b3b3; margin-bottom: 4px; }
|
|
130
|
+
.widget-item strong { font-size: 14px; font-weight: 500; line-height: 1.4; }
|
|
131
|
+
.widget-item span { color: #c3c4c2; font-size: 12px; line-height: 1.6; margin-top: 4px; }
|
|
132
|
+
.widget-empty { font-size: 13px; line-height: 1.6; color: #aaa; margin: 0 0 12px; }
|
|
133
|
+
.shortcut-row { display: flex; align-items: center; gap: 8px; border-top: 1px solid #ffffff12; }
|
|
134
|
+
.shortcut-row:first-child { border-top: 0; }
|
|
135
|
+
.shortcut-link { flex: 1; min-width: 0; background: none; border: 0; text-align: left; color: #d0d1ce; font: 11px / 1.5 ui-monospace, 'Cascadia Mono', monospace; padding: 8px 0; overflow-wrap: anywhere; }
|
|
136
|
+
.shortcut-link::after { content: ' ↗'; color: #a7d7e5; }
|
|
137
|
+
.pin-shortcut { white-space: nowrap; }
|
|
138
|
+
.widget .pin-shortcut, .widget-tools button { color: #bfc0bb; font: 9px / 1.4 ui-monospace, monospace; border-radius: 3px; padding: 5px 7px; background: #ffffff06; }
|
|
139
|
+
.editing { min-height: 688px; background: repeating-linear-gradient(0deg, #ffffff03 0px, #ffffff03 160px, transparent 160px, transparent 176px); border-radius: 18px; }
|
|
140
|
+
.widget-tools { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 12px; flex-shrink: 0; }
|
|
141
|
+
.widget-tools button { padding: 5px 8px; }
|
|
142
|
+
.widget-drag { cursor: grab; touch-action: none; background: none; border: 0; color: #aaa; font-size: 24px; padding: 0 6px; }
|
|
143
|
+
.editing .widget { cursor: grab; touch-action: none; user-select: none; animation: none; }
|
|
144
|
+
.editing .widget.dragging { z-index: 4; cursor: grabbing; border-color: #a7d7e5; box-shadow: 0 16px 40px #0008; will-change: transform; transition: none; }
|
|
145
|
+
.widget-preview { pointer-events: none; background: #a7d7e51a; border: 1px dashed #a7d7e5; box-shadow: none; animation: none; }
|
|
146
|
+
.widget-preview::before { display: none; }
|
|
147
|
+
.widget-preview.blocked { background: #e8909014; border-color: #e89090; }
|
|
148
|
+
.widget.dragging.blocked { border-color: #e89090; }
|
|
149
|
+
.widget-resize { position: absolute; z-index: 2; padding: 0; border: 0; background: transparent; touch-action: none; }
|
|
150
|
+
.widget-resize:hover, .widget-resize:focus-visible { background: #a7d7e550; }
|
|
151
|
+
.resize-n, .resize-s { left: 14px; right: 14px; height: 10px; cursor: ns-resize; }
|
|
152
|
+
.resize-n { top: 0; } .resize-s { bottom: 0; }
|
|
153
|
+
.resize-e, .resize-w { top: 14px; bottom: 14px; width: 10px; cursor: ew-resize; }
|
|
154
|
+
.resize-e { right: 0; } .resize-w { left: 0; }
|
|
155
|
+
.resize-ne, .resize-nw, .resize-se, .resize-sw { width: 16px; height: 16px; }
|
|
156
|
+
.resize-ne, .resize-sw { cursor: nesw-resize; } .resize-nw, .resize-se { cursor: nwse-resize; }
|
|
157
|
+
.resize-ne { top: 0; right: 0; } .resize-nw { top: 0; left: 0; }
|
|
158
|
+
.resize-se { bottom: 0; right: 0; border-right: 2px solid #a7d7e580; border-bottom: 2px solid #a7d7e580; border-radius: 0 0 7px 0; }
|
|
159
|
+
.resize-sw { bottom: 0; left: 0; }
|
|
160
|
+
.col-0 { grid-column-start: 1; } .col-1 { grid-column-start: 2; } .col-2 { grid-column-start: 3; } .col-3 { grid-column-start: 4; }
|
|
161
|
+
.row-0 { grid-row-start: 1; } .row-1 { grid-row-start: 2; } .row-2 { grid-row-start: 3; } .row-3 { grid-row-start: 4; }
|
|
162
|
+
.row-4 { grid-row-start: 5; } .row-5 { grid-row-start: 6; } .row-6 { grid-row-start: 7; } .row-7 { grid-row-start: 8; }
|
|
163
|
+
.size-small { grid-column-end: span 1; grid-row-end: span 1; }
|
|
164
|
+
.size-wide { grid-column-end: span 2; grid-row-end: span 1; }
|
|
165
|
+
.size-large { grid-column-end: span 2; grid-row-end: span 2; }
|
|
166
|
+
.size-full { grid-column-end: span 4; grid-row-end: span 2; }
|
|
167
|
+
#ai-answer { max-width: 1000px; }
|
|
168
|
+
#ai-answer-title { font-size: 28px; font-weight: 700; letter-spacing: -.7px; overflow-wrap: anywhere; }
|
|
169
|
+
#answer-text { max-width: 720px; }
|
|
170
|
+
.answer-view { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr)); gap: 16px; margin: 28px 0 40px; }
|
|
171
|
+
.widget.generated-card { min-height: 0; max-height: none; padding: 24px; }
|
|
172
|
+
.generated-card::after { content: ''; pointer-events: none; position: absolute; z-index: -1; right: -20px; top: 0; width: 180px; height: 180px; background: url('/assets/widget-announcements.svg') center / contain no-repeat; opacity: .07; }
|
|
173
|
+
.generated-card.answer-checklist::after { background-image: url('/assets/widget-shortcuts.svg'); }
|
|
174
|
+
.generated-card .widget-edition { margin-bottom: 28px; }
|
|
175
|
+
.generated-card .widget-header { margin: 0 0 14px; }
|
|
176
|
+
.generated-card h3 { font-size: 23px; line-height: 1.2; overflow-wrap: anywhere; }
|
|
177
|
+
.generated-card p, .generated-card li { font-size: 14px; color: #d0d1ce; line-height: 1.65; overflow-wrap: anywhere; }
|
|
178
|
+
.answer-progress, .answer-session-note { color: #aaa; font: 11px / 1.5 ui-monospace, monospace; }
|
|
179
|
+
.answer-checklist { list-style: none; margin: 4px 0 20px; padding: 0; }
|
|
180
|
+
.answer-checklist li { border-top: 1px solid #ffffff12; padding: 16px 0; }
|
|
181
|
+
.answer-checklist label { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; }
|
|
182
|
+
.answer-checklist input { appearance: auto; flex: 0 0 18px; width: 18px; height: 18px; margin: 3px 0 0; accent-color: #a7d7e5; }
|
|
183
|
+
.answer-checklist input:focus-visible { outline: 2px solid #a7d7e5; outline-offset: 4px; }
|
|
184
|
+
.answer-checklist .completed label span { text-decoration: line-through; color: #888; }
|
|
185
|
+
.answer-references { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
186
|
+
.answer-references a { color: #b1d9e8; font: 10px / 1.5 ui-monospace, monospace; text-decoration: none; padding: 3px 6px; border: 1px solid #ffffff1a; border-radius: 3px; }
|
|
187
|
+
.answer-references a:hover { background: #ffffff09; text-decoration: underline; }
|
|
188
|
+
.answer-checklist .answer-references { margin-left: 30px; }
|
|
189
|
+
.answer-comparison { grid-column: 1 / -1; }
|
|
190
|
+
.answer-table-scroll { overflow-x: auto; }
|
|
191
|
+
.answer-table-scroll table { border-collapse: collapse; width: 100%; min-width: 420px; text-align: left; color: #d0d1ce; font-size: 13px; }
|
|
192
|
+
.answer-table-scroll th, .answer-table-scroll td { padding: 16px 12px; border-top: 1px solid #ffffff16; vertical-align: top; overflow-wrap: anywhere; max-width: 320px; white-space: pre-wrap; }
|
|
193
|
+
.answer-table-scroll thead th { font: 10px / 1.5 ui-monospace, monospace; color: #aaa; }
|
|
194
|
+
.answer-table-scroll tbody th { font-weight: 500; }
|
|
195
|
+
.answer-table-scroll .answer-references { margin: 0; }
|
|
196
|
+
.answer-source-cards > section { padding: 18px 0; border-top: 1px solid #ffffff12; }
|
|
197
|
+
.answer-source-cards h4 { margin: 0 0 8px; font-size: 16px; letter-spacing: -.3px; }
|
|
198
|
+
.answer-source-cards p { margin: 0; white-space: pre-wrap; }
|
|
199
|
+
.answer-sources-heading { font: 11px / 1.5 ui-monospace, monospace; color: #aaa; margin: 32px 0 12px; }
|
|
200
|
+
#answer-sources { max-width: 720px; }
|
|
201
|
+
#answer-sources .source { scroll-margin-top: 24px; }
|
|
202
|
+
@container (max-width: 300px) {
|
|
203
|
+
.widget-mark { opacity: .17; width: 65%; }
|
|
204
|
+
.widget-body { width: 100%; }
|
|
205
|
+
.widget h3 { font-size: 17px; letter-spacing: -.4px; }
|
|
206
|
+
}
|
|
207
|
+
@media (max-width: 760px) {
|
|
208
|
+
.widget-grid { display: flex; flex-direction: column; min-height: 0; }
|
|
209
|
+
.widget { max-height: 360px; min-height: 160px; }
|
|
210
|
+
.widget.size-large, .widget.size-full { min-height: 260px; }
|
|
211
|
+
.home-widgets { overflow-x: auto; }
|
|
212
|
+
.widget-grid.editing { display: grid; width: 800px; min-height: 688px; }
|
|
213
|
+
.widget-grid.editing .widget { min-height: 0; max-height: none; }
|
|
214
|
+
.widgets-heading { align-items: flex-start; flex-direction: column; gap: 14px; }
|
|
215
|
+
.home-widgets { margin-top: 40px; }
|
|
216
|
+
}
|
|
217
|
+
@media (max-width: 700px) {
|
|
218
|
+
.workspace { padding: 80px 20px 40px 80px; }
|
|
219
|
+
.home { transform: none; }
|
|
220
|
+
h1 { font-size: 20px; line-height: 1.4; margin-bottom: 14px; letter-spacing: -.3px; }
|
|
221
|
+
.composer { padding: 16px; height: 120px; border-radius: 32px; }
|
|
222
|
+
.workspace.has-results { padding-top: 72px; }
|
|
223
|
+
select { max-width: 90%; }
|
|
224
|
+
}
|
|
225
|
+
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }
|
|
226
|
+
@media (prefers-reduced-motion: reduce) { .dot-loader > span { opacity: calc(.95 - var(--ring) * .125); will-change: auto; } }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const sizes = { small: [1, 1], wide: [2, 1], large: [2, 2], full: [4, 2],
|
|
2
|
+
...Object.fromEntries(Array.from({ length: 32 }, (_, i) => {
|
|
3
|
+
const w = i % 4 + 1, h = Math.floor(i / 4) + 1;
|
|
4
|
+
return [`${w}x${h}`, [w, h]];
|
|
5
|
+
})) };
|
|
6
|
+
export const defaults = () => [
|
|
7
|
+
{ type: 'deadlines', x: 0, y: 0, size: 'wide' },
|
|
8
|
+
{ type: 'announcements', x: 2, y: 0, size: 'large' },
|
|
9
|
+
{ type: 'shortcuts', x: 0, y: 1, size: 'wide' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function fits(layout, widget) {
|
|
13
|
+
if (!Object.hasOwn(sizes, widget.size) || !Number.isInteger(widget.x) || !Number.isInteger(widget.y)) return false;
|
|
14
|
+
const [w, h] = sizes[widget.size];
|
|
15
|
+
if (widget.x < 0 || widget.x + w > 4 || widget.y < 0 || widget.y + h > 8) return false;
|
|
16
|
+
return layout.every(other => {
|
|
17
|
+
if (other.type === widget.type) return true;
|
|
18
|
+
const [ow, oh] = sizes[other.size];
|
|
19
|
+
return widget.x + w <= other.x || other.x + ow <= widget.x || widget.y + h <= other.y || other.y + oh <= widget.y;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function restore(value) {
|
|
24
|
+
if (!value || !Array.isArray(value.layout) || value.layout.length > 3 || !Array.isArray(value.pins)) return null;
|
|
25
|
+
const layout = [];
|
|
26
|
+
for (const item of value.layout) {
|
|
27
|
+
if (!item || !['deadlines', 'announcements', 'shortcuts'].includes(item.type) || layout.some(w => w.type === item.type) || !fits(layout, item)) return null;
|
|
28
|
+
layout.push({ type: item.type, x: item.x, y: item.y, size: item.size });
|
|
29
|
+
}
|
|
30
|
+
return { layout, pins: [...new Set(value.pins.filter(pin => typeof pin === 'string' && pin.length < 2000))].slice(0, 20) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function firstSpace(layout, type, size = 'wide') {
|
|
34
|
+
for (let y = 0; y < 8; y++) for (let x = 0; x < 4; x++) {
|
|
35
|
+
const widget = { type, size, x, y };
|
|
36
|
+
if (fits(layout, widget)) return widget;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|