@learnpack/learnpack 5.0.70 → 5.0.72
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/README.md +13 -13
- package/lib/commands/init.js +1 -1
- package/lib/commands/serve.js +60 -4
- package/lib/creatorDist/assets/{index-Dqo9u2iR.css → index-BJ2JJzVC.css} +53 -26
- package/lib/creatorDist/assets/{index-Chx6V3zd.js → index-CKBeex0S.js} +35878 -29623
- package/lib/creatorDist/index.html +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +1 -1
- package/src/commands/serve.ts +70 -6
- package/src/creator/package-lock.json +49 -0
- package/src/creator/package.json +1 -0
- package/src/creator/src/App.tsx +28 -21
- package/src/creator/src/assets/svgs.tsx +1 -1
- package/src/creator/src/components/ConsumablesManager.tsx +12 -2
- package/src/creator/src/components/LessonItem.tsx +3 -2
- package/src/creator/src/components/Loader.tsx +5 -1
- package/src/creator/src/components/Login.tsx +58 -151
- package/src/creator/src/components/Message.tsx +11 -1
- package/src/creator/src/components/Redirector.tsx +12 -0
- package/src/creator/src/components/syllabus/ContentIndex.tsx +88 -58
- package/src/creator/src/components/syllabus/Sidebar.tsx +3 -12
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +63 -7
- package/src/creator/src/index.css +15 -0
- package/src/creator/src/main.tsx +0 -1
- package/src/creator/src/utils/creatorUtils.ts +33 -3
- package/src/creator/src/utils/lib.ts +156 -2
- package/src/creator/src/utils/rigo.ts +3 -3
- package/src/creator/src/utils/store.ts +2 -1
- package/src/creatorDist/assets/{index-Dqo9u2iR.css → index-BJ2JJzVC.css} +53 -26
- package/src/creatorDist/assets/{index-Chx6V3zd.js → index-CKBeex0S.js} +35878 -29623
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +529 -529
- package/src/ui/app.tar.gz +0 -0
@@ -6,12 +6,15 @@ import {
|
|
6
6
|
parseLesson,
|
7
7
|
uploadFileToBucket,
|
8
8
|
useConsumableCall,
|
9
|
+
validateTokens,
|
10
|
+
extractImagesFromMarkdown,
|
9
11
|
} from "../../utils/lib"
|
10
12
|
import {
|
11
13
|
createLearnJson,
|
12
14
|
processExercise,
|
13
15
|
slugify,
|
14
16
|
randomUUID,
|
17
|
+
processImage,
|
15
18
|
} from "../../utils/creatorUtils"
|
16
19
|
|
17
20
|
import Loader from "../Loader"
|
@@ -21,10 +24,25 @@ import { ConsumablesManager } from "../ConsumablesManager"
|
|
21
24
|
import toast from "react-hot-toast"
|
22
25
|
import { ContentIndex } from "./ContentIndex"
|
23
26
|
import { Sidebar } from "./Sidebar"
|
27
|
+
import Login from "../Login"
|
28
|
+
import { eventBus } from "../../utils/eventBus"
|
24
29
|
|
25
30
|
const SyllabusEditor: React.FC = () => {
|
26
|
-
const [messages, setMessages] = useState<TMessage[]>([
|
31
|
+
const [messages, setMessages] = useState<TMessage[]>([
|
32
|
+
{
|
33
|
+
type: "assistant",
|
34
|
+
content: "If you're satisfied, type 'OK' in the chat.",
|
35
|
+
},
|
36
|
+
{
|
37
|
+
type: "assistant",
|
38
|
+
content:
|
39
|
+
"If not, what would you like me to change? You can sat things like: 'Add more exercises', 'Make it more difficult', 'Remove step 1.1 and replace it with a new step that explains the concept of X'",
|
40
|
+
},
|
41
|
+
])
|
27
42
|
const [isGenerating, setIsGenerating] = useState(false)
|
43
|
+
const [showLoginModal, setShowLoginModal] = useState(false)
|
44
|
+
const [isThinking, setIsThinking] = useState(false)
|
45
|
+
|
28
46
|
const prevLessons = useRef<Lesson[]>([])
|
29
47
|
const { syllabus, setSyllabus, auth } = useStore(
|
30
48
|
useShallow((state) => ({
|
@@ -35,20 +53,22 @@ const SyllabusEditor: React.FC = () => {
|
|
35
53
|
)
|
36
54
|
|
37
55
|
const sendPrompt = async (prompt: string) => {
|
56
|
+
setIsThinking(true)
|
57
|
+
|
38
58
|
setMessages([
|
39
59
|
...messages,
|
40
60
|
{ type: "user", content: prompt },
|
41
61
|
{ type: "assistant", content: "" },
|
42
62
|
])
|
43
63
|
prevLessons.current = syllabus.lessons
|
44
|
-
const res = await interactiveCreation(
|
64
|
+
const res = await interactiveCreation({
|
45
65
|
courseInfo: JSON.stringify(syllabus),
|
46
66
|
prevInteractions:
|
47
67
|
messages
|
48
68
|
.map((message) => `${message.type}: ${message.content}`)
|
49
69
|
.join("\n") + `\nUSER: ${prompt}`,
|
50
70
|
})
|
51
|
-
|
71
|
+
|
52
72
|
const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
|
53
73
|
parseLesson(step)
|
54
74
|
)
|
@@ -65,9 +85,19 @@ const SyllabusEditor: React.FC = () => {
|
|
65
85
|
newMessages[newMessages.length - 1].content = res.parsed.aiMessage
|
66
86
|
return newMessages
|
67
87
|
})
|
88
|
+
setIsThinking(false)
|
68
89
|
}
|
69
90
|
|
70
91
|
const handleSubmit = async () => {
|
92
|
+
if (!auth.bcToken || !auth.rigoToken) {
|
93
|
+
setShowLoginModal(true)
|
94
|
+
return
|
95
|
+
}
|
96
|
+
const isValid = await validateTokens(auth.bcToken)
|
97
|
+
if (!isValid) {
|
98
|
+
setShowLoginModal(true)
|
99
|
+
return
|
100
|
+
}
|
71
101
|
const success = await useConsumableCall(auth.bcToken, "ai-generation")
|
72
102
|
if (!success) {
|
73
103
|
toast.error("You don't have enough credits to generate a course!")
|
@@ -75,18 +105,35 @@ const SyllabusEditor: React.FC = () => {
|
|
75
105
|
}
|
76
106
|
setIsGenerating(true)
|
77
107
|
|
108
|
+
const tutorialDir =
|
109
|
+
"courses/" + slugify(syllabus.courseInfo.title || randomUUID())
|
78
110
|
const lessonsPromises = syllabus.lessons.map((lesson) =>
|
79
111
|
processExercise(
|
80
112
|
auth.rigoToken,
|
81
113
|
syllabus.lessons,
|
82
114
|
JSON.stringify(syllabus.courseInfo),
|
83
115
|
lesson,
|
84
|
-
"
|
85
|
-
slugify(syllabus.courseInfo.title || randomUUID()) +
|
86
|
-
"/exercises"
|
116
|
+
tutorialDir + "/exercises"
|
87
117
|
)
|
88
118
|
)
|
89
|
-
await Promise.all(lessonsPromises)
|
119
|
+
const readmeContents = await Promise.all(lessonsPromises)
|
120
|
+
|
121
|
+
let imagesArray: any[] = []
|
122
|
+
|
123
|
+
for (const content of readmeContents) {
|
124
|
+
imagesArray = [...imagesArray, ...extractImagesFromMarkdown(content)]
|
125
|
+
}
|
126
|
+
|
127
|
+
eventBus.emit("course-generation", {
|
128
|
+
message: "📷 Generating images...",
|
129
|
+
})
|
130
|
+
|
131
|
+
const imagePromises = imagesArray.map(
|
132
|
+
async (image: { alt: string; url: string }) => {
|
133
|
+
return processImage(tutorialDir, image.url, image.alt, auth.rigoToken)
|
134
|
+
}
|
135
|
+
)
|
136
|
+
await Promise.all(imagePromises)
|
90
137
|
|
91
138
|
const learnJson = createLearnJson(syllabus.courseInfo)
|
92
139
|
await uploadFileToBucket(
|
@@ -112,6 +159,14 @@ It may take a moment..."
|
|
112
159
|
/>
|
113
160
|
) : (
|
114
161
|
<div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
|
162
|
+
{showLoginModal && (
|
163
|
+
<Login
|
164
|
+
onFinish={() => {
|
165
|
+
setShowLoginModal(false)
|
166
|
+
}}
|
167
|
+
/>
|
168
|
+
)}
|
169
|
+
|
115
170
|
<ConsumablesManager />
|
116
171
|
|
117
172
|
<Sidebar
|
@@ -125,6 +180,7 @@ It may take a moment..."
|
|
125
180
|
prevLessons={prevLessons.current}
|
126
181
|
handleSubmit={handleSubmit}
|
127
182
|
messages={messages}
|
183
|
+
isThinking={isThinking}
|
128
184
|
/>
|
129
185
|
</div>
|
130
186
|
</div>
|
@@ -126,3 +126,18 @@ h1 {
|
|
126
126
|
}
|
127
127
|
}
|
128
128
|
}
|
129
|
+
|
130
|
+
.border-learnpack-blue {
|
131
|
+
border-color: var(--learnpack-blue);
|
132
|
+
}
|
133
|
+
|
134
|
+
.red-ball {
|
135
|
+
width: 16px;
|
136
|
+
height: 16px;
|
137
|
+
border: 2px solid white;
|
138
|
+
background-color: #eb5757;
|
139
|
+
border-radius: 50%;
|
140
|
+
position: absolute;
|
141
|
+
top: -10px;
|
142
|
+
left: 10px;
|
143
|
+
}
|
package/src/creator/src/main.tsx
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
import { Lesson } from "../components/LessonItem"
|
2
2
|
import { eventBus } from "./eventBus"
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
generateImage,
|
5
|
+
getFilenameFromUrl,
|
6
|
+
uploadFileToBucket,
|
7
|
+
uploadImageToBucket,
|
8
|
+
} from "./lib"
|
4
9
|
import { makeReadmeReadable, readmeCreator, checkReadability } from "./rigo"
|
5
10
|
import { FormState } from "./store"
|
6
11
|
|
@@ -80,8 +85,6 @@ export async function processExercise(
|
|
80
85
|
expected_grade_level: PARAMS.expected_grade_level,
|
81
86
|
})
|
82
87
|
|
83
|
-
// console.log("REDUCED README START", reducedReadme, "REDUCED README END")
|
84
|
-
|
85
88
|
if (!reducedReadme) break
|
86
89
|
|
87
90
|
readability = checkReadability(
|
@@ -134,3 +137,30 @@ export async function processExercise(
|
|
134
137
|
export const randomUUID = () => {
|
135
138
|
return Math.random().toString(36).substring(2, 15)
|
136
139
|
}
|
140
|
+
|
141
|
+
export const processImage = async (
|
142
|
+
tutorialDir: string,
|
143
|
+
url: string,
|
144
|
+
description: string,
|
145
|
+
rigoToken: string
|
146
|
+
) => {
|
147
|
+
try {
|
148
|
+
const filename = getFilenameFromUrl(url)
|
149
|
+
|
150
|
+
const imagePath = tutorialDir + "/.learn" + "/assets/" + filename
|
151
|
+
|
152
|
+
eventBus.emit("course-generation", {
|
153
|
+
message: `🖼️ Generating image ${imagePath}`,
|
154
|
+
})
|
155
|
+
|
156
|
+
const res = await generateImage(rigoToken, { prompt: description })
|
157
|
+
await uploadImageToBucket(res.image_url, imagePath)
|
158
|
+
|
159
|
+
eventBus.emit("course-generation", {
|
160
|
+
message: `✅ Image ${imagePath} generated successfully!`,
|
161
|
+
})
|
162
|
+
return true
|
163
|
+
} catch {
|
164
|
+
return false
|
165
|
+
}
|
166
|
+
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import axios from "axios"
|
2
|
-
import { BREATHECODE_HOST } from "./constants"
|
2
|
+
import { BREATHECODE_HOST, RIGOBOT_HOST } from "./constants"
|
3
3
|
|
4
4
|
type ParsedLesson = {
|
5
5
|
id: string
|
@@ -26,7 +26,7 @@ export function parseLesson(input: string): ParsedLesson | null {
|
|
26
26
|
}
|
27
27
|
}
|
28
28
|
|
29
|
-
|
29
|
+
export const CREATOR_API_URL = "http://localhost:3000"
|
30
30
|
|
31
31
|
export const uploadFileToBucket = async (content: string, path: string) => {
|
32
32
|
const response = await axios.post(`/upload`, {
|
@@ -35,6 +35,13 @@ export const uploadFileToBucket = async (content: string, path: string) => {
|
|
35
35
|
})
|
36
36
|
return response.data
|
37
37
|
}
|
38
|
+
export const uploadImageToBucket = async (imageUrl: string, path: string) => {
|
39
|
+
const response = await axios.post(`/upload-image`, {
|
40
|
+
image_url: imageUrl,
|
41
|
+
destination: path,
|
42
|
+
})
|
43
|
+
return response.data
|
44
|
+
}
|
38
45
|
|
39
46
|
export const checkParams = () => {
|
40
47
|
const urlParams = new URLSearchParams(window.location.search)
|
@@ -120,3 +127,150 @@ export const parseConsumables = (
|
|
120
127
|
|
121
128
|
return result
|
122
129
|
}
|
130
|
+
|
131
|
+
type LoginInfo = {
|
132
|
+
email: string
|
133
|
+
password: string
|
134
|
+
}
|
135
|
+
|
136
|
+
export const getRigobotJSON = async (breathecodeToken: string) => {
|
137
|
+
const rigoUrl = `${RIGOBOT_HOST}/v1/auth/me/token?breathecode_token=${breathecodeToken}`
|
138
|
+
const rigoResp = await fetch(rigoUrl)
|
139
|
+
if (!rigoResp.ok) {
|
140
|
+
throw new Error("Unable to obtain Rigobot token")
|
141
|
+
}
|
142
|
+
const rigobotJson = await rigoResp.json()
|
143
|
+
return rigobotJson
|
144
|
+
}
|
145
|
+
export const validateUser = async (breathecodeToken: string) => {
|
146
|
+
const config = {
|
147
|
+
method: "GET",
|
148
|
+
headers: {
|
149
|
+
"Content-Type": "application/json",
|
150
|
+
Authorization: `Token ${breathecodeToken}`,
|
151
|
+
},
|
152
|
+
}
|
153
|
+
|
154
|
+
const res = await fetch(`${BREATHECODE_HOST}/v1/auth/user/me`, config)
|
155
|
+
if (!res.ok) {
|
156
|
+
console.log("ERROR", res)
|
157
|
+
return null
|
158
|
+
}
|
159
|
+
const json = await res.json()
|
160
|
+
|
161
|
+
if ("roles" in json) {
|
162
|
+
delete json.roles
|
163
|
+
}
|
164
|
+
if ("permissions" in json) {
|
165
|
+
delete json.permissions
|
166
|
+
}
|
167
|
+
if ("settings" in json) {
|
168
|
+
delete json.settings
|
169
|
+
}
|
170
|
+
|
171
|
+
return json
|
172
|
+
}
|
173
|
+
|
174
|
+
export const login4Geeks = async (loginInfo: LoginInfo) => {
|
175
|
+
const url = `${BREATHECODE_HOST}/v1/auth/login/`
|
176
|
+
|
177
|
+
const res = await fetch(url, {
|
178
|
+
body: JSON.stringify(loginInfo),
|
179
|
+
method: "post",
|
180
|
+
headers: {
|
181
|
+
"Content-Type": "application/json",
|
182
|
+
},
|
183
|
+
})
|
184
|
+
|
185
|
+
if (!res.ok) {
|
186
|
+
throw Error("Unable to login with provided credentials")
|
187
|
+
}
|
188
|
+
|
189
|
+
const json = await res.json()
|
190
|
+
|
191
|
+
const rigoJson = await getRigobotJSON(json.token)
|
192
|
+
|
193
|
+
const user = await validateUser(json.token)
|
194
|
+
const returns = { ...json, rigobot: { ...rigoJson }, user }
|
195
|
+
|
196
|
+
return returns
|
197
|
+
}
|
198
|
+
|
199
|
+
export const loginWithToken = async (token: string) => {
|
200
|
+
const rigoJson = await getRigobotJSON(token)
|
201
|
+
|
202
|
+
const user = await validateUser(token)
|
203
|
+
|
204
|
+
const returns = { rigobot: { ...rigoJson }, ...user }
|
205
|
+
|
206
|
+
return returns
|
207
|
+
}
|
208
|
+
|
209
|
+
export const validateTokens = async (breathecodeToken: string) => {
|
210
|
+
const user = await validateUser(breathecodeToken)
|
211
|
+
console.log("USER", user)
|
212
|
+
if (!user) {
|
213
|
+
return false
|
214
|
+
}
|
215
|
+
|
216
|
+
const rigobotJson = await getRigobotJSON(breathecodeToken)
|
217
|
+
console.log("RIGOBOT", rigobotJson)
|
218
|
+
|
219
|
+
return true
|
220
|
+
}
|
221
|
+
|
222
|
+
export function extractImagesFromMarkdown(markdown: string) {
|
223
|
+
const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
|
224
|
+
const images = []
|
225
|
+
let match
|
226
|
+
|
227
|
+
while ((match = imageRegex.exec(markdown)) !== null) {
|
228
|
+
const altText = match[1]
|
229
|
+
const url = match[2]
|
230
|
+
images.push({ alt: altText, url: url })
|
231
|
+
}
|
232
|
+
|
233
|
+
return images
|
234
|
+
}
|
235
|
+
|
236
|
+
export function getFilenameFromUrl(url: string): string {
|
237
|
+
try {
|
238
|
+
// 1) Use the URL constructor to strip off protocol/host/search/hash
|
239
|
+
const pathname = new URL(url, location.href).pathname
|
240
|
+
// 2) Grab everything after the last “/”
|
241
|
+
return pathname.substring(pathname.lastIndexOf("/") + 1)
|
242
|
+
} catch {
|
243
|
+
// Fallback for non-absolute URLs or invalid inputs
|
244
|
+
const clean = url.split("?")[0].split("#")[0]
|
245
|
+
return clean.substring(clean.lastIndexOf("/") + 1)
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
type TGenerateImageParams = {
|
250
|
+
prompt: string
|
251
|
+
}
|
252
|
+
|
253
|
+
export const generateImage = async (
|
254
|
+
token: string,
|
255
|
+
{ prompt }: TGenerateImageParams
|
256
|
+
) => {
|
257
|
+
try {
|
258
|
+
const response = await axios.post(
|
259
|
+
`${RIGOBOT_HOST}/v1/learnpack/tools/images`,
|
260
|
+
{
|
261
|
+
prompt,
|
262
|
+
},
|
263
|
+
{
|
264
|
+
headers: {
|
265
|
+
"Content-Type": "application/json",
|
266
|
+
Authorization: "Token " + token,
|
267
|
+
},
|
268
|
+
}
|
269
|
+
)
|
270
|
+
|
271
|
+
return response.data
|
272
|
+
} catch (error) {
|
273
|
+
console.error("Error generating image:", error)
|
274
|
+
return null
|
275
|
+
}
|
276
|
+
}
|
@@ -10,11 +10,11 @@ type TInteractiveCreationInputs = {
|
|
10
10
|
prevInteractions: string
|
11
11
|
}
|
12
12
|
export const interactiveCreation = async (
|
13
|
-
token: string,
|
13
|
+
// token: string,
|
14
14
|
inputs: TInteractiveCreationInputs
|
15
15
|
) => {
|
16
16
|
const response = await axios.post(
|
17
|
-
`${RIGOBOT_HOST}/v1/prompting/completion/390/`,
|
17
|
+
`${RIGOBOT_HOST}/v1/prompting/public/completion/390/`,
|
18
18
|
{
|
19
19
|
inputs: inputs,
|
20
20
|
include_purpose_objective: false,
|
@@ -23,7 +23,7 @@ export const interactiveCreation = async (
|
|
23
23
|
{
|
24
24
|
headers: {
|
25
25
|
"Content-Type": "application/json",
|
26
|
-
Authorization: "Token " + token,
|
26
|
+
// Authorization: "Token " + token,
|
27
27
|
},
|
28
28
|
}
|
29
29
|
)
|
@@ -18,6 +18,7 @@ type Auth = {
|
|
18
18
|
bcToken: string
|
19
19
|
rigoToken: string
|
20
20
|
userId: string
|
21
|
+
user: any
|
21
22
|
}
|
22
23
|
export type Syllabus = {
|
23
24
|
lessons: Lesson[]
|
@@ -50,6 +51,7 @@ const useStore = create<Store>()(
|
|
50
51
|
bcToken: "",
|
51
52
|
rigoToken: "",
|
52
53
|
userId: "",
|
54
|
+
user: null,
|
53
55
|
},
|
54
56
|
formState: {
|
55
57
|
description: "",
|
@@ -62,7 +64,6 @@ const useStore = create<Store>()(
|
|
62
64
|
variables: [
|
63
65
|
"description",
|
64
66
|
"duration",
|
65
|
-
"login",
|
66
67
|
"targetAudience",
|
67
68
|
"hasContentIndex",
|
68
69
|
],
|
@@ -57,7 +57,6 @@
|
|
57
57
|
--color-red-300: oklch(80.8% 0.114 19.571);
|
58
58
|
--color-red-500: oklch(63.7% 0.237 25.331);
|
59
59
|
--color-red-700: oklch(50.5% 0.213 27.518);
|
60
|
-
--color-yellow-50: oklch(98.7% 0.026 102.212);
|
61
60
|
--color-sky-500: oklch(68.5% 0.169 237.323);
|
62
61
|
--color-sky-600: oklch(58.8% 0.158 241.966);
|
63
62
|
--color-blue-50: oklch(97% 0.014 254.604);
|
@@ -78,6 +77,7 @@
|
|
78
77
|
--color-gray-700: oklch(37.3% 0.034 259.733);
|
79
78
|
--color-gray-800: oklch(27.8% 0.033 256.848);
|
80
79
|
--color-gray-900: oklch(21% 0.034 264.665);
|
80
|
+
--color-black: #000;
|
81
81
|
--color-white: #fff;
|
82
82
|
--spacing: 0.25rem;
|
83
83
|
--container-sm: 24rem;
|
@@ -86,8 +86,6 @@
|
|
86
86
|
--text-sm--line-height: calc(1.25 / 0.875);
|
87
87
|
--text-lg: 1.125rem;
|
88
88
|
--text-lg--line-height: calc(1.75 / 1.125);
|
89
|
-
--text-xl: 1.25rem;
|
90
|
-
--text-xl--line-height: calc(1.75 / 1.25);
|
91
89
|
--text-4xl: 2.25rem;
|
92
90
|
--text-4xl--line-height: calc(2.5 / 2.25);
|
93
91
|
--font-weight-medium: 500;
|
@@ -373,6 +371,9 @@
|
|
373
371
|
.relative {
|
374
372
|
position: relative;
|
375
373
|
}
|
374
|
+
.inset-0 {
|
375
|
+
inset: calc(var(--spacing) * 0);
|
376
|
+
}
|
376
377
|
.-top-1 {
|
377
378
|
top: calc(var(--spacing) * -1);
|
378
379
|
}
|
@@ -421,6 +422,9 @@
|
|
421
422
|
.z-50 {
|
422
423
|
z-index: 50;
|
423
424
|
}
|
425
|
+
.z-1000 {
|
426
|
+
z-index: 1000;
|
427
|
+
}
|
424
428
|
.container {
|
425
429
|
width: 100%;
|
426
430
|
}
|
@@ -455,9 +459,6 @@
|
|
455
459
|
.mx-2 {
|
456
460
|
margin-inline: calc(var(--spacing) * 2);
|
457
461
|
}
|
458
|
-
.mx-auto {
|
459
|
-
margin-inline: auto;
|
460
|
-
}
|
461
462
|
.mt-1 {
|
462
463
|
margin-top: calc(var(--spacing) * 1);
|
463
464
|
}
|
@@ -470,9 +471,6 @@
|
|
470
471
|
.mt-6 {
|
471
472
|
margin-top: calc(var(--spacing) * 6);
|
472
473
|
}
|
473
|
-
.mt-10 {
|
474
|
-
margin-top: calc(var(--spacing) * 10);
|
475
|
-
}
|
476
474
|
.mr-1 {
|
477
475
|
margin-right: calc(var(--spacing) * 1);
|
478
476
|
}
|
@@ -521,8 +519,11 @@
|
|
521
519
|
.h-40 {
|
522
520
|
height: calc(var(--spacing) * 40);
|
523
521
|
}
|
524
|
-
.h
|
525
|
-
height:
|
522
|
+
.h-60 {
|
523
|
+
height: calc(var(--spacing) * 60);
|
524
|
+
}
|
525
|
+
.h-\[85\%\] {
|
526
|
+
height: 85%;
|
526
527
|
}
|
527
528
|
.h-full {
|
528
529
|
height: 100%;
|
@@ -530,12 +531,15 @@
|
|
530
531
|
.h-screen {
|
531
532
|
height: 100vh;
|
532
533
|
}
|
533
|
-
.max-h-\[
|
534
|
-
max-height:
|
534
|
+
.max-h-\[80vh\] {
|
535
|
+
max-height: 80vh;
|
535
536
|
}
|
536
537
|
.max-h-\[300px\] {
|
537
538
|
max-height: 300px;
|
538
539
|
}
|
540
|
+
.min-h-\[70vh\] {
|
541
|
+
min-height: 70vh;
|
542
|
+
}
|
539
543
|
.min-h-screen {
|
540
544
|
min-height: 100vh;
|
541
545
|
}
|
@@ -717,6 +721,18 @@
|
|
717
721
|
.border-transparent {
|
718
722
|
border-color: #0000;
|
719
723
|
}
|
724
|
+
.bg-black\/50 {
|
725
|
+
background-color: #00000080;
|
726
|
+
}
|
727
|
+
@supports (color: color-mix(in lab, red, red)) {
|
728
|
+
.bg-black\/50 {
|
729
|
+
background-color: color-mix(
|
730
|
+
in oklab,
|
731
|
+
var(--color-black) 50%,
|
732
|
+
transparent
|
733
|
+
);
|
734
|
+
}
|
735
|
+
}
|
720
736
|
.bg-blue-50 {
|
721
737
|
background-color: var(--color-blue-50);
|
722
738
|
}
|
@@ -744,9 +760,6 @@
|
|
744
760
|
.bg-white {
|
745
761
|
background-color: var(--color-white);
|
746
762
|
}
|
747
|
-
.bg-yellow-50 {
|
748
|
-
background-color: var(--color-yellow-50);
|
749
|
-
}
|
750
763
|
.bg-gradient-to-t {
|
751
764
|
--tw-gradient-position: to top in oklab;
|
752
765
|
background-image: linear-gradient(var(--tw-gradient-stops));
|
@@ -829,10 +842,6 @@
|
|
829
842
|
font-size: var(--text-sm);
|
830
843
|
line-height: var(--tw-leading, var(--text-sm--line-height));
|
831
844
|
}
|
832
|
-
.text-xl {
|
833
|
-
font-size: var(--text-xl);
|
834
|
-
line-height: var(--tw-leading, var(--text-xl--line-height));
|
835
|
-
}
|
836
845
|
.text-\[10px\] {
|
837
846
|
font-size: 10px;
|
838
847
|
}
|
@@ -897,12 +906,6 @@
|
|
897
906
|
.opacity-30 {
|
898
907
|
opacity: 0.3;
|
899
908
|
}
|
900
|
-
.shadow {
|
901
|
-
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a),
|
902
|
-
0 1px 2px -1px var(--tw-shadow-color, #0000001a);
|
903
|
-
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
|
904
|
-
var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
905
|
-
}
|
906
909
|
.shadow-md {
|
907
910
|
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, #0000001a),
|
908
911
|
0 2px 4px -2px var(--tw-shadow-color, #0000001a);
|
@@ -915,6 +918,17 @@
|
|
915
918
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
|
916
919
|
var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
917
920
|
}
|
921
|
+
.transition {
|
922
|
+
transition-property: color, background-color, border-color, outline-color,
|
923
|
+
text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via,
|
924
|
+
--tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate,
|
925
|
+
filter, -webkit-backdrop-filter, backdrop-filter;
|
926
|
+
transition-timing-function: var(
|
927
|
+
--tw-ease,
|
928
|
+
var(--default-transition-timing-function)
|
929
|
+
);
|
930
|
+
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
931
|
+
}
|
918
932
|
.transition-all {
|
919
933
|
transition-property: all;
|
920
934
|
transition-timing-function: var(
|
@@ -1091,6 +1105,19 @@ h1 {
|
|
1091
1105
|
.blue-on-hover:hover svg path {
|
1092
1106
|
fill: var(--learnpack-blue);
|
1093
1107
|
}
|
1108
|
+
.border-learnpack-blue {
|
1109
|
+
border-color: var(--learnpack-blue);
|
1110
|
+
}
|
1111
|
+
.red-ball {
|
1112
|
+
background-color: #eb5757;
|
1113
|
+
border: 2px solid #fff;
|
1114
|
+
border-radius: 50%;
|
1115
|
+
width: 16px;
|
1116
|
+
height: 16px;
|
1117
|
+
position: absolute;
|
1118
|
+
top: -10px;
|
1119
|
+
left: 10px;
|
1120
|
+
}
|
1094
1121
|
@property --tw-translate-x {
|
1095
1122
|
syntax: "*";
|
1096
1123
|
inherits: false;
|