@learnpack/learnpack 5.0.355 → 5.0.357
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/lib/commands/serve.d.ts +0 -17
- package/lib/commands/serve.js +159 -129
- package/lib/creatorDist/assets/{index-DnthLsvb.js → index-D6pmbMe9.js} +14030 -13903
- package/lib/creatorDist/assets/index-zrPponAn.css +1701 -0
- package/lib/creatorDist/index.html +2 -2
- package/lib/models/creator.d.ts +1 -1
- package/lib/services/ingest/adapters/gcsCourseStorage.d.ts +13 -0
- package/lib/services/ingest/adapters/gcsCourseStorage.js +58 -0
- package/lib/services/ingest/adapters/rigoUserTokenAuth.d.ts +15 -0
- package/lib/services/ingest/adapters/rigoUserTokenAuth.js +46 -0
- package/lib/services/ingest/adapters/rigobotPackageRegistry.d.ts +7 -0
- package/lib/services/ingest/adapters/rigobotPackageRegistry.js +119 -0
- package/lib/services/ingest/core/buildIngestConfig.d.ts +27 -0
- package/lib/services/ingest/core/buildIngestConfig.js +40 -0
- package/lib/services/ingest/core/buildInitialSyllabus.d.ts +24 -0
- package/lib/services/ingest/core/buildInitialSyllabus.js +104 -0
- package/lib/services/ingest/core/buildSidebar.d.ts +13 -0
- package/lib/services/ingest/core/buildSidebar.js +28 -0
- package/lib/services/ingest/core/coursePaths.d.ts +31 -0
- package/lib/services/ingest/core/coursePaths.js +50 -0
- package/lib/services/ingest/core/ingestCoursePackage.d.ts +52 -0
- package/lib/services/ingest/core/ingestCoursePackage.js +217 -0
- package/lib/services/ingest/core/normalizeLearnJson.d.ts +25 -0
- package/lib/services/ingest/core/normalizeLearnJson.js +29 -0
- package/lib/services/ingest/core/pairLessons.d.ts +29 -0
- package/lib/services/ingest/core/pairLessons.js +102 -0
- package/lib/services/ingest/core/planWrites.d.ts +44 -0
- package/lib/services/ingest/core/planWrites.js +59 -0
- package/lib/services/ingest/core/resolveSlug.d.ts +53 -0
- package/lib/services/ingest/core/resolveSlug.js +93 -0
- package/lib/services/ingest/core/types.d.ts +53 -0
- package/lib/services/ingest/core/types.js +2 -0
- package/lib/services/ingest/core/validatePackage.d.ts +38 -0
- package/lib/services/ingest/core/validatePackage.js +97 -0
- package/lib/services/ingest/createIngestService.d.ts +34 -0
- package/lib/services/ingest/createIngestService.js +38 -0
- package/lib/services/ingest/errors.d.ts +22 -0
- package/lib/services/ingest/errors.js +34 -0
- package/lib/services/ingest/http/errorMapping.d.ts +8 -0
- package/lib/services/ingest/http/errorMapping.js +40 -0
- package/lib/services/ingest/http/router.d.ts +32 -0
- package/lib/services/ingest/http/router.js +124 -0
- package/lib/services/ingest/http/zipPackageReader.d.ts +26 -0
- package/lib/services/ingest/http/zipPackageReader.js +62 -0
- package/lib/services/ingest/ports/courseStorage.d.ts +25 -0
- package/lib/services/ingest/ports/courseStorage.js +2 -0
- package/lib/services/ingest/ports/packageRegistry.d.ts +58 -0
- package/lib/services/ingest/ports/packageRegistry.js +2 -0
- package/lib/services/ingest/ports/requestAuthenticator.d.ts +25 -0
- package/lib/services/ingest/ports/requestAuthenticator.js +2 -0
- package/lib/utils/coursePackage/bucketIo.d.ts +23 -0
- package/lib/utils/coursePackage/bucketIo.js +36 -0
- package/lib/utils/coursePackage/learnJson.d.ts +26 -0
- package/lib/utils/coursePackage/learnJson.js +36 -0
- package/lib/utils/coursePackage/sidebar.d.ts +15 -0
- package/lib/utils/coursePackage/sidebar.js +26 -0
- package/lib/utils/creatorSocket.js +15 -21
- package/lib/utils/gcpCredentials.d.ts +18 -0
- package/lib/utils/gcpCredentials.js +27 -0
- package/lib/utils/rigoActions.d.ts +16 -0
- package/lib/utils/rigoActions.js +102 -1
- package/lib/utils/socketRegistry.d.ts +46 -0
- package/lib/utils/socketRegistry.js +83 -0
- package/package.json +1 -1
- package/src/commands/serve.ts +162 -114
- package/src/creator/README.md +63 -51
- package/src/creator/package-lock.json +1188 -8
- package/src/creator/package.json +5 -1
- package/src/creator/src/App.tsx +120 -54
- package/src/creator/src/components/FileUploader.tsx +1 -12
- package/src/creator/src/components/NotificationListener.tsx +1 -8
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +101 -36
- package/src/creator/src/locales/en.json +8 -0
- package/src/creator/src/locales/es.json +8 -0
- package/src/creator/src/utils/completionResult.test.ts +144 -0
- package/src/creator/src/utils/completionResult.ts +154 -0
- package/src/creator/src/utils/constants.ts +17 -4
- package/src/creator/src/utils/socket.test.ts +113 -0
- package/src/creator/src/utils/socket.ts +60 -37
- package/src/creator/src/utils/store.ts +84 -67
- package/src/creator/src/utils/useCompletionWatchdog.test.ts +99 -0
- package/src/creator/src/utils/useCompletionWatchdog.ts +37 -0
- package/src/creator/vitest.config.ts +16 -0
- package/src/creatorDist/assets/{index-DnthLsvb.js → index-D6pmbMe9.js} +14030 -13903
- package/src/creatorDist/assets/index-zrPponAn.css +1701 -0
- package/src/creatorDist/index.html +2 -2
- package/src/models/creator.ts +1 -1
- package/src/services/ingest/adapters/gcsCourseStorage.ts +70 -0
- package/src/services/ingest/adapters/rigoUserTokenAuth.ts +65 -0
- package/src/services/ingest/adapters/rigobotPackageRegistry.ts +161 -0
- package/src/services/ingest/core/buildIngestConfig.ts +51 -0
- package/src/services/ingest/core/buildInitialSyllabus.ts +122 -0
- package/src/services/ingest/core/buildSidebar.ts +32 -0
- package/src/services/ingest/core/coursePaths.ts +59 -0
- package/src/services/ingest/core/ingestCoursePackage.ts +343 -0
- package/src/services/ingest/core/normalizeLearnJson.ts +47 -0
- package/src/services/ingest/core/pairLessons.ts +132 -0
- package/src/services/ingest/core/planWrites.ts +91 -0
- package/src/services/ingest/core/resolveSlug.ts +119 -0
- package/src/services/ingest/core/types.ts +57 -0
- package/src/services/ingest/core/validatePackage.ts +132 -0
- package/src/services/ingest/createIngestService.ts +66 -0
- package/src/services/ingest/errors.ts +45 -0
- package/src/services/ingest/http/errorMapping.ts +49 -0
- package/src/services/ingest/http/router.ts +156 -0
- package/src/services/ingest/http/zipPackageReader.ts +81 -0
- package/src/services/ingest/ports/courseStorage.ts +25 -0
- package/src/services/ingest/ports/packageRegistry.ts +60 -0
- package/src/services/ingest/ports/requestAuthenticator.ts +28 -0
- package/src/ui/_app/app.js +325 -325
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/coursePackage/bucketIo.ts +46 -0
- package/src/utils/coursePackage/learnJson.ts +37 -0
- package/src/utils/coursePackage/sidebar.ts +28 -0
- package/src/utils/creatorSocket.ts +16 -22
- package/src/utils/gcpCredentials.ts +38 -0
- package/src/utils/rigoActions.ts +163 -0
- package/src/utils/socketRegistry.ts +85 -0
- package/lib/creatorDist/assets/index-CjddKHB_.css +0 -1
- package/src/creatorDist/assets/index-CjddKHB_.css +0 -1
package/src/ui/app.tar.gz
CHANGED
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Buffer } from "buffer"
|
|
2
|
+
import { Bucket } from "@google-cloud/storage"
|
|
3
|
+
import { sanitizeReadmeNewlines } from "../readmeSanitizer"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Writes a text file to the course bucket.
|
|
7
|
+
*
|
|
8
|
+
* READMEs are sanitized on the way in: the newline collapsing has to happen at
|
|
9
|
+
* the single write path, otherwise a caller that forgets it stores content the
|
|
10
|
+
* translation step later chokes on.
|
|
11
|
+
* @param bucket - Target GCS bucket.
|
|
12
|
+
* @param file - File contents as a string.
|
|
13
|
+
* @param path - Full object path inside the bucket.
|
|
14
|
+
* @returns Resolves once the object is stored.
|
|
15
|
+
*/
|
|
16
|
+
export const uploadFileToBucket = async (
|
|
17
|
+
bucket: Bucket,
|
|
18
|
+
file: string,
|
|
19
|
+
path: string
|
|
20
|
+
) => {
|
|
21
|
+
const isReadme = /readme(\.\w+)?\.md$/i.test(path)
|
|
22
|
+
const content = isReadme ? sanitizeReadmeNewlines(file) : file
|
|
23
|
+
const fileRef = bucket.file(path)
|
|
24
|
+
await fileRef.save(Buffer.from(content, "utf8"))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Writes a binary file (images, assets) to the course bucket.
|
|
29
|
+
* @param bucket - Target GCS bucket.
|
|
30
|
+
* @param buffer - File contents as a buffer.
|
|
31
|
+
* @param path - Full object path inside the bucket.
|
|
32
|
+
* @param contentType - Optional MIME type to store alongside the object.
|
|
33
|
+
* @returns Resolves once the object is stored.
|
|
34
|
+
*/
|
|
35
|
+
export const uploadBinaryToBucket = async (
|
|
36
|
+
bucket: Bucket,
|
|
37
|
+
buffer: Buffer,
|
|
38
|
+
path: string,
|
|
39
|
+
contentType?: string
|
|
40
|
+
) => {
|
|
41
|
+
const fileRef = bucket.file(path)
|
|
42
|
+
await fileRef.save(buffer, {
|
|
43
|
+
resumable: false,
|
|
44
|
+
...(contentType && { contentType }),
|
|
45
|
+
})
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BREATHECODE_TELEMETRY_URL } from "../api"
|
|
2
|
+
import { FormState } from "../../models/creator"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the `learn.json` for a freshly created course.
|
|
6
|
+
*
|
|
7
|
+
* The preview URL is derived from the slug rather than stored: the published
|
|
8
|
+
* package always lives at `{slug}.learn-pack.com`, so deriving it keeps the two
|
|
9
|
+
* from drifting apart when the slug changes.
|
|
10
|
+
* @param courseInfo - Course metadata collected by the creator wizard.
|
|
11
|
+
* @returns The `learn.json` object, ready to be serialized.
|
|
12
|
+
*/
|
|
13
|
+
export const createLearnJson = (courseInfo: FormState) => {
|
|
14
|
+
// console.log("courseInfo to create learn json", courseInfo)
|
|
15
|
+
|
|
16
|
+
const expectedPreviewUrl = `https://${courseInfo.slug}.learn-pack.com/preview.png`
|
|
17
|
+
|
|
18
|
+
const language = courseInfo.language || "en"
|
|
19
|
+
|
|
20
|
+
const learnJson = {
|
|
21
|
+
slug: courseInfo.slug,
|
|
22
|
+
title: {
|
|
23
|
+
[language]: courseInfo.title,
|
|
24
|
+
},
|
|
25
|
+
technologies: courseInfo.technologies || [],
|
|
26
|
+
difficulty: "beginner",
|
|
27
|
+
description: {
|
|
28
|
+
[language]: courseInfo.description,
|
|
29
|
+
},
|
|
30
|
+
grading: "isolated",
|
|
31
|
+
telemetry: {
|
|
32
|
+
batch: BREATHECODE_TELEMETRY_URL,
|
|
33
|
+
},
|
|
34
|
+
preview: expectedPreviewUrl,
|
|
35
|
+
}
|
|
36
|
+
return learnJson
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TSidebar } from "../sidebarGenerator"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds the initial sidebar for a course whose lessons have no title yet.
|
|
5
|
+
*
|
|
6
|
+
* Each entry maps to its own slug as a placeholder: at creation time the
|
|
7
|
+
* generator has not produced titles, and the sidebar is rewritten once it does.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the helpers in `sidebarGenerator`, this one is pure — it returns the
|
|
10
|
+
* sidebar instead of writing it to disk, because the bucket path persists it
|
|
11
|
+
* through the storage layer rather than through the local filesystem.
|
|
12
|
+
* @param slugs - Exercise slugs, in the order they should appear.
|
|
13
|
+
* @param initialLanguage - Language code the placeholder titles are filed under.
|
|
14
|
+
* @returns The sidebar object, ready to be serialized.
|
|
15
|
+
*/
|
|
16
|
+
export const createInitialSidebar = async (
|
|
17
|
+
slugs: string[],
|
|
18
|
+
initialLanguage = "en"
|
|
19
|
+
): Promise<TSidebar> => {
|
|
20
|
+
const sidebar: TSidebar = {}
|
|
21
|
+
for (const slug of slugs) {
|
|
22
|
+
sidebar[slug] = {
|
|
23
|
+
[initialLanguage]: slug,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return sidebar
|
|
28
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Server as SocketIOServer, Socket } from "socket.io"
|
|
2
|
+
import { SocketRegistry } from "./socketRegistry"
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const notificationSocketMap = new Map<string, Set<string>>() // notificationId -> Set<socket.id>
|
|
4
|
+
const courseRegistry = new SocketRegistry() // courseSlug -> socket ids
|
|
5
|
+
const notificationRegistry = new SocketRegistry() // notificationId -> socket ids
|
|
6
6
|
const socketStore = new Map<string, Socket>() // socket.id -> socket
|
|
7
7
|
|
|
8
8
|
let io: SocketIOServer | null = null
|
|
@@ -24,11 +24,7 @@ export function initSocketIO(server: any) {
|
|
|
24
24
|
const { courseSlug } = data
|
|
25
25
|
if (!courseSlug) return
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
courseSocketMap.set(courseSlug, new Set())
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
courseSocketMap.get(courseSlug)?.add(socket.id)
|
|
27
|
+
courseRegistry.register(courseSlug, socket.id)
|
|
32
28
|
console.log(`📦 Socket ${socket.id} registered to course: ${courseSlug}`)
|
|
33
29
|
})
|
|
34
30
|
|
|
@@ -36,11 +32,7 @@ export function initSocketIO(server: any) {
|
|
|
36
32
|
const { notificationId } = data
|
|
37
33
|
if (!notificationId) return
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
notificationSocketMap.set(notificationId, new Set())
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
notificationSocketMap.get(notificationId)?.add(socket.id)
|
|
35
|
+
notificationRegistry.register(notificationId, socket.id)
|
|
44
36
|
console.log(
|
|
45
37
|
`📧 Socket ${socket.id} registered to notification: ${notificationId}`
|
|
46
38
|
)
|
|
@@ -50,9 +42,10 @@ export function initSocketIO(server: any) {
|
|
|
50
42
|
console.log("🔥 Socket disconnected:", socket.id)
|
|
51
43
|
socketStore.delete(socket.id)
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
// Both registries, not just the course one. Leaving the socket in the
|
|
46
|
+
// notification registry leaked it for the lifetime of the process.
|
|
47
|
+
courseRegistry.removeSocket(socket.id)
|
|
48
|
+
notificationRegistry.removeSocket(socket.id)
|
|
56
49
|
})
|
|
57
50
|
})
|
|
58
51
|
|
|
@@ -60,10 +53,7 @@ export function initSocketIO(server: any) {
|
|
|
60
53
|
}
|
|
61
54
|
|
|
62
55
|
export function emitToCourse(courseSlug: string, event: string, payload: any) {
|
|
63
|
-
const
|
|
64
|
-
if (!socketIds || socketIds.size === 0) return
|
|
65
|
-
|
|
66
|
-
for (const id of socketIds) {
|
|
56
|
+
for (const id of courseRegistry.listeners(courseSlug)) {
|
|
67
57
|
const socket = socketStore.get(id)
|
|
68
58
|
if (socket) socket.emit(event, payload)
|
|
69
59
|
}
|
|
@@ -74,8 +64,12 @@ export function emitToNotification(
|
|
|
74
64
|
payload: any,
|
|
75
65
|
retry = 0
|
|
76
66
|
) {
|
|
77
|
-
const socketIds =
|
|
78
|
-
|
|
67
|
+
const socketIds = notificationRegistry.listeners(notificationId)
|
|
68
|
+
|
|
69
|
+
// The webhook can beat the browser to the registration: the creator only
|
|
70
|
+
// subscribes once the initial POST resolves, and a job that fails fast comes
|
|
71
|
+
// back sooner than that.
|
|
72
|
+
if (socketIds.length === 0) {
|
|
79
73
|
if (retry > 3) {
|
|
80
74
|
console.log("❌ Notification", notificationId, "not found")
|
|
81
75
|
return
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCP service-account credentials, required and parsed rather than assumed.
|
|
3
|
+
*
|
|
4
|
+
* Every caller today re-reads `GCP_CREDENTIALS_JSON` inline and handles it a
|
|
5
|
+
* little differently: `serve.ts` exits silently when it is missing and then
|
|
6
|
+
* parses without a try/catch, so malformed JSON surfaces as a bare
|
|
7
|
+
* SyntaxError with no hint about which variable produced it. Validating in one
|
|
8
|
+
* place turns both cases into a message that names the variable.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors `requireGcsBucketName` and `requireAwsCredentials`, including their
|
|
11
|
+
* refusal of the empty-string fallback.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type GcpCredentials = {
|
|
15
|
+
client_email?: string;
|
|
16
|
+
private_key?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function requireGcpCredentials(): GcpCredentials {
|
|
21
|
+
const raw = (process.env.GCP_CREDENTIALS_JSON || "").trim()
|
|
22
|
+
|
|
23
|
+
if (!raw) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"GCP_CREDENTIALS_JSON (env) is required: it holds the service account that reaches the course bucket"
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`GCP_CREDENTIALS_JSON (env) is not valid JSON: ${
|
|
34
|
+
(error as Error).message
|
|
35
|
+
}`
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/utils/rigoActions.ts
CHANGED
|
@@ -147,6 +147,169 @@ export const translateExercise = async (
|
|
|
147
147
|
return response.data
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
export type TCompletionJob = {
|
|
151
|
+
id: number;
|
|
152
|
+
status: "PENDING" | "SUCCESS" | "ERROR" | string;
|
|
153
|
+
status_text?: string | null;
|
|
154
|
+
parsed?: Record<string, any> | null;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
type TTranslateAndWaitOptions = {
|
|
158
|
+
/** How often the job status is checked while it is still running. */
|
|
159
|
+
pollIntervalMs?: number;
|
|
160
|
+
/** How long we keep waiting before giving up on the job. */
|
|
161
|
+
timeoutMs?: number;
|
|
162
|
+
/** Called after every successful poll, useful to keep a heartbeat alive. */
|
|
163
|
+
onPoll?: (job: TCompletionJob) => void | Promise<void>;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const POST_RETRY_DELAYS_MS = [1000, 3000, 6000]
|
|
167
|
+
const MAX_CONSECUTIVE_POLL_ERRORS = 5
|
|
168
|
+
|
|
169
|
+
const sleep = (ms: number) =>
|
|
170
|
+
new Promise<void>(resolve => {
|
|
171
|
+
setTimeout(resolve, ms)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const isFinalStatus = (status: string) =>
|
|
175
|
+
status === "SUCCESS" || status === "ERROR"
|
|
176
|
+
|
|
177
|
+
// Network failures and 5xx are worth retrying, a 4xx is not going to fix itself.
|
|
178
|
+
const isRetriablePostError = (error: any): boolean => {
|
|
179
|
+
const status = error?.response?.status
|
|
180
|
+
return status === undefined || status >= 500
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const assertJobSucceeded = (job: TCompletionJob): TCompletionJob => {
|
|
184
|
+
if (job.status === "ERROR") {
|
|
185
|
+
throw new Error(
|
|
186
|
+
job.status_text || `Rigobot translation job ${job.id} finished with ERROR`
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return job
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const getCompletionJob = async (
|
|
194
|
+
token: string,
|
|
195
|
+
completionId: number | string
|
|
196
|
+
): Promise<TCompletionJob> => {
|
|
197
|
+
const response = await axios.get(
|
|
198
|
+
`${RIGOBOT_HOST}/v1/prompting/completion/${completionId}/`,
|
|
199
|
+
{
|
|
200
|
+
headers: {
|
|
201
|
+
"Content-Type": "application/json",
|
|
202
|
+
Authorization: "Token " + token,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return response.data
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Translates an exercise as an asynchronous Rigobot job and waits for the
|
|
211
|
+
// result by polling.
|
|
212
|
+
//
|
|
213
|
+
// Running the completion synchronously (execute_async: false) keeps the HTTP
|
|
214
|
+
// request open until the LLM is done, which for a full README takes 28-45s and
|
|
215
|
+
// gets killed by Heroku's 30s router limit. Firing the job asynchronously
|
|
216
|
+
// answers in about a second and moves the waiting to the polling loop, so it
|
|
217
|
+
// is no longer bound to the lifetime of a single request.
|
|
218
|
+
export const translateExerciseAndWait = async (
|
|
219
|
+
token: string,
|
|
220
|
+
inputs: TTranslateInputs,
|
|
221
|
+
options: TTranslateAndWaitOptions = {}
|
|
222
|
+
): Promise<TCompletionJob> => {
|
|
223
|
+
const { pollIntervalMs = 3000, timeoutMs = 6 * 60 * 1000, onPoll } = options
|
|
224
|
+
|
|
225
|
+
let job: TCompletionJob | undefined
|
|
226
|
+
|
|
227
|
+
for (let attempt = 0; attempt <= POST_RETRY_DELAYS_MS.length; attempt++) {
|
|
228
|
+
try {
|
|
229
|
+
// eslint-disable-next-line no-await-in-loop
|
|
230
|
+
const response = await axios.post(
|
|
231
|
+
`${RIGOBOT_HOST}/v1/prompting/completion/translate-asset-markdown/`,
|
|
232
|
+
{
|
|
233
|
+
inputs: inputs,
|
|
234
|
+
include_purpose_objective: false,
|
|
235
|
+
execute_async: true,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
headers: {
|
|
239
|
+
"Content-Type": "application/json",
|
|
240
|
+
Authorization: "Token " + token,
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
job = response.data
|
|
246
|
+
break
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (
|
|
249
|
+
!isRetriablePostError(error) ||
|
|
250
|
+
attempt === POST_RETRY_DELAYS_MS.length
|
|
251
|
+
) {
|
|
252
|
+
throw error
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// eslint-disable-next-line no-await-in-loop
|
|
256
|
+
await sleep(POST_RETRY_DELAYS_MS[attempt])
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!job) {
|
|
261
|
+
throw new Error("Rigobot did not return a translation job")
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Rigobot answers straight away with the cached completion when the very
|
|
265
|
+
// same inputs were translated before, so there is nothing to wait for.
|
|
266
|
+
if (isFinalStatus(job.status)) {
|
|
267
|
+
return assertJobSucceeded(job)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const jobId = job.id
|
|
271
|
+
const startedAt = Date.now()
|
|
272
|
+
let consecutivePollErrors = 0
|
|
273
|
+
|
|
274
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
275
|
+
// eslint-disable-next-line no-await-in-loop
|
|
276
|
+
await sleep(pollIntervalMs)
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
// eslint-disable-next-line no-await-in-loop
|
|
280
|
+
job = await getCompletionJob(token, jobId)
|
|
281
|
+
consecutivePollErrors = 0
|
|
282
|
+
} catch (error) {
|
|
283
|
+
consecutivePollErrors++
|
|
284
|
+
|
|
285
|
+
if (consecutivePollErrors >= MAX_CONSECUTIVE_POLL_ERRORS) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Could not read the status of the translation job ${jobId} after ${MAX_CONSECUTIVE_POLL_ERRORS} attempts: ${
|
|
288
|
+
(error as Error).message
|
|
289
|
+
}`
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (onPoll) {
|
|
297
|
+
// eslint-disable-next-line no-await-in-loop
|
|
298
|
+
await onPoll(job)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (isFinalStatus(job.status)) {
|
|
302
|
+
return assertJobSucceeded(job)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
throw new Error(
|
|
307
|
+
`The translation job ${jobId} did not finish after ${Math.round(
|
|
308
|
+
timeoutMs / 1000
|
|
309
|
+
)}s, its last known status was ${job.status}`
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
150
313
|
type TGenerateCourseIntroductionInputs = {
|
|
151
314
|
course_title: string;
|
|
152
315
|
lessons_context: string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which sockets are listening on which channel.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `creatorSocket.ts` so the bookkeeping can be tested without
|
|
5
|
+
* standing up a socket.io server. The registry deals in socket ids only; the
|
|
6
|
+
* socket objects themselves stay with the transport layer.
|
|
7
|
+
*
|
|
8
|
+
* The behaviour that matters is `removeSocket`: a disconnecting client has to
|
|
9
|
+
* disappear from **every** channel it joined. The notification map used to be
|
|
10
|
+
* skipped on disconnect, so it grew for the lifetime of the process and
|
|
11
|
+
* accumulated ids of sockets that were long gone.
|
|
12
|
+
*/
|
|
13
|
+
export class SocketRegistry {
|
|
14
|
+
private readonly channels = new Map<string, Set<string>>()
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adds a socket to a channel.
|
|
18
|
+
* @param channel - Channel key, such as a course slug or a notification id.
|
|
19
|
+
* @param socketId - The socket joining it.
|
|
20
|
+
*/
|
|
21
|
+
register(channel: string, socketId: string): void {
|
|
22
|
+
if (!channel || !socketId) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let listeners = this.channels.get(channel)
|
|
27
|
+
if (!listeners) {
|
|
28
|
+
listeners = new Set<string>()
|
|
29
|
+
this.channels.set(channel, listeners)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
listeners.add(socketId)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Removes a socket from one channel, dropping the channel when it empties.
|
|
37
|
+
* @param channel - Channel to leave.
|
|
38
|
+
* @param socketId - The socket leaving it.
|
|
39
|
+
*/
|
|
40
|
+
unregister(channel: string, socketId: string): void {
|
|
41
|
+
const listeners = this.channels.get(channel)
|
|
42
|
+
if (!listeners) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
listeners.delete(socketId)
|
|
47
|
+
if (listeners.size === 0) {
|
|
48
|
+
this.channels.delete(channel)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Removes a socket from every channel it joined.
|
|
54
|
+
*
|
|
55
|
+
* Called when the socket disconnects. Without it the registry keeps growing
|
|
56
|
+
* with ids that can never receive anything again.
|
|
57
|
+
* @param socketId - The socket that went away.
|
|
58
|
+
*/
|
|
59
|
+
removeSocket(socketId: string): void {
|
|
60
|
+
for (const [channel, listeners] of this.channels) {
|
|
61
|
+
listeners.delete(socketId)
|
|
62
|
+
if (listeners.size === 0) {
|
|
63
|
+
this.channels.delete(channel)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The sockets currently listening on a channel.
|
|
70
|
+
* @param channel - Channel to look up.
|
|
71
|
+
* @returns Their socket ids, empty when nobody is listening.
|
|
72
|
+
*/
|
|
73
|
+
listeners(channel: string): string[] {
|
|
74
|
+
const listeners = this.channels.get(channel)
|
|
75
|
+
return listeners ? [...listeners] : []
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* How many channels are being tracked. Exposed for tests and diagnostics.
|
|
80
|
+
* @returns The number of non-empty channels.
|
|
81
|
+
*/
|
|
82
|
+
channelCount(): number {
|
|
83
|
+
return this.channels.size
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/*! tailwindcss v4.1.3 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:rotateX(0);--tw-rotate-y:rotateY(0);--tw-rotate-z:rotateZ(0);--tw-skew-x:skewX(0);--tw-skew-y:skewY(0);--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-7xl:4.5rem;--text-7xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.-top-3{top:calc(var(--spacing)*-3)}.top-0{top:calc(var(--spacing)*0)}.top-2{top:calc(var(--spacing)*2)}.right-2{right:calc(var(--spacing)*2)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-2{bottom:calc(var(--spacing)*2)}.bottom-5{bottom:calc(var(--spacing)*5)}.bottom-10{bottom:calc(var(--spacing)*10)}.bottom-\[100\%\]{bottom:100%}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.z-10{z-index:10}.z-30{z-index:30}.z-31{z-index:31}.z-50{z-index:50}.z-1000{z-index:1000}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.-mt-5{margin-top:calc(var(--spacing)*-5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-14{margin-top:calc(var(--spacing)*14)}.mr-1{margin-right:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-5{margin-bottom:calc(var(--spacing)*5)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-6{margin-left:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-15{height:calc(var(--spacing)*15)}.h-20{height:calc(var(--spacing)*20)}.h-24{height:calc(var(--spacing)*24)}.h-60{height:calc(var(--spacing)*60)}.h-\[50vh\]{height:50vh}.h-\[85\%\]{height:85%}.h-\[150px\]{height:150px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[78vh\]{max-height:78vh}.max-h-\[300px\]{max-height:300px}.min-h-\[69vh\]{min-height:69vh}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-4\/5{width:80%}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-15{width:calc(var(--spacing)*15)}.w-20{width:calc(var(--spacing)*20)}.w-100{width:calc(var(--spacing)*100)}.w-\[100px\]{width:100px}.w-\[280px\]{width:280px}.w-full{width:100%}.max-w-\[700px\]{max-width:700px}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-\[250px\]{min-width:250px}.flex-1{flex:1}.flex-grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-disc{list-style-type:disc}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[50\%\]{border-radius:50%}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-600{border-color:var(--color-blue-600)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-transparent{border-color:#0000}.border-t-transparent{border-top-color:#0000}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-white{background-color:var(--color-white)}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-white{--tw-gradient-from:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-10{padding:calc(var(--spacing)*10)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-20{padding-inline:calc(var(--spacing)*20)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.pr-2{padding-right:calc(var(--spacing)*2)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-20{padding-bottom:calc(var(--spacing)*20)}.pb-32{padding-bottom:calc(var(--spacing)*32)}.pl-5{padding-left:calc(var(--spacing)*5)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.whitespace-pre-line{white-space:pre-line}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-900{color:var(--color-blue-900)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.uppercase{text-transform:uppercase}.opacity-25{opacity:.25}.opacity-30{opacity:.3}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media (hover:hover){.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-yellow-600:hover{color:var(--color-yellow-600)}.hover\:text-yellow-700:hover{color:var(--color-yellow-700)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:w-auto{width:auto}.sm\:flex-row{flex-direction:row}:where(.sm\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*0)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*0)*calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.sm\:p-4{padding:calc(var(--spacing)*4)}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}@media (min-width:48rem){.md\:flex-row{flex-direction:row}.md\:p-6{padding:calc(var(--spacing)*6)}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:relative{position:relative}.lg\:hidden{display:none}.lg\:w-1\/3{width:33.3333%}.lg\:translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:transform-none{transform:none}.lg\:p-8{padding:calc(var(--spacing)*8)}}@media (min-width:80rem){.xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}}:root{color-scheme:light dark;color:#213547;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--four-geeks-blue:#0097cf;--soft-blue:#f3fafd;--gray-text:#6883b4;--learnpack-blue:#02a9ea;--learnpack-light-blue:#c7f3fd;font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;font-weight:400;line-height:1.5}a{color:var(--learnpack-blue);-webkit-text-decoration:inherit;text-decoration:inherit;font-weight:500}a:hover{color:var(--learnpack-blue)}body{background-color:#f4f9fe;min-height:100vh;margin:0}.bg-learnpack-blue{background-color:var(--soft-blue)}.bg-learnpack{background-color:var(--learnpack-blue)}.bg-gray-blue{background-color:#e7f1ff}.index-circle{width:30px;height:30px;color:var(--four-geeks-blue);background-color:var(--soft-blue);border-radius:50%;justify-content:center;align-items:center;font-size:16px;font-weight:600;display:flex}.text-gray{color:var(--gray-text)}.scrollbar-hide{scrollbar-width:none!important}.loader{justify-content:center;align-items:center;gap:10px;animation:1s linear infinite glowing;display:flex}@keyframes spin{to{transform:rotate(360deg)}}.loader-icon{justify-content:center;align-items:center;width:40px;height:40px;display:flex;position:relative}@keyframes glowing{0%{opacity:.6}to{opacity:1}}.blue-on-hover:hover svg,.blue-on-hover:hover svg path{fill:var(--learnpack-blue)}.border-learnpack-blue{border-color:var(--learnpack-blue)}.red-ball{background-color:#eb5757;border:2px solid #fff;border-radius:50%;width:16px;height:16px;position:absolute;top:-10px;left:10px}.fluid-svg>svg{width:100%;height:100%}.outline-blue:focus{outline:1px solid #51b3e5}.border-C8DBFC{border-color:#c8dbfc}.markdown-renderer ul{padding-left:0;list-style-type:disc;list-style-position:inside}.markdown-renderer ol{padding-left:0;list-style-type:decimal;list-style-position:inside}.appear{transform-origin:top;opacity:0;animation:.5s ease-in-out forwards appear;transform:scaleY(0)translateY(20px)}@keyframes appear{0%{opacity:0;transform:scaleY(0)translateY(20px)}to{opacity:1;transform:scaleY(1)translateY(0)}}.bg-heavy-blue{background-color:#9fbdf0}.border-heavy-blue{border-color:#9fbdf0}.border-heavy-blue:focus{outline:1px solid #9fbdf0}.cloudy{opacity:0;transition:opacity .5s ease-in-out;animation:.5s ease-in-out forwards fadeIn}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.yellow-ball{background-color:#08f700;border-radius:50%;width:16px;height:16px;position:absolute;top:-10px;left:10px}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/*! tailwindcss v4.1.3 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:rotateX(0);--tw-rotate-y:rotateY(0);--tw-rotate-z:rotateZ(0);--tw-skew-x:skewX(0);--tw-skew-y:skewY(0);--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-7xl:4.5rem;--text-7xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.-top-3{top:calc(var(--spacing)*-3)}.top-0{top:calc(var(--spacing)*0)}.top-2{top:calc(var(--spacing)*2)}.right-2{right:calc(var(--spacing)*2)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-2{bottom:calc(var(--spacing)*2)}.bottom-5{bottom:calc(var(--spacing)*5)}.bottom-10{bottom:calc(var(--spacing)*10)}.bottom-\[100\%\]{bottom:100%}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.z-10{z-index:10}.z-30{z-index:30}.z-31{z-index:31}.z-50{z-index:50}.z-1000{z-index:1000}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.-mt-5{margin-top:calc(var(--spacing)*-5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-14{margin-top:calc(var(--spacing)*14)}.mr-1{margin-right:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-5{margin-bottom:calc(var(--spacing)*5)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-6{margin-left:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-15{height:calc(var(--spacing)*15)}.h-20{height:calc(var(--spacing)*20)}.h-24{height:calc(var(--spacing)*24)}.h-60{height:calc(var(--spacing)*60)}.h-\[50vh\]{height:50vh}.h-\[85\%\]{height:85%}.h-\[150px\]{height:150px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[78vh\]{max-height:78vh}.max-h-\[300px\]{max-height:300px}.min-h-\[69vh\]{min-height:69vh}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-4\/5{width:80%}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-15{width:calc(var(--spacing)*15)}.w-20{width:calc(var(--spacing)*20)}.w-100{width:calc(var(--spacing)*100)}.w-\[100px\]{width:100px}.w-\[280px\]{width:280px}.w-full{width:100%}.max-w-\[700px\]{max-width:700px}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-\[250px\]{min-width:250px}.flex-1{flex:1}.flex-grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-disc{list-style-type:disc}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[50\%\]{border-radius:50%}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-600{border-color:var(--color-blue-600)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-transparent{border-color:#0000}.border-t-transparent{border-top-color:#0000}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-white{background-color:var(--color-white)}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-white{--tw-gradient-from:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-10{padding:calc(var(--spacing)*10)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-20{padding-inline:calc(var(--spacing)*20)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.pr-2{padding-right:calc(var(--spacing)*2)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-20{padding-bottom:calc(var(--spacing)*20)}.pb-32{padding-bottom:calc(var(--spacing)*32)}.pl-5{padding-left:calc(var(--spacing)*5)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.whitespace-pre-line{white-space:pre-line}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-900{color:var(--color-blue-900)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.uppercase{text-transform:uppercase}.opacity-25{opacity:.25}.opacity-30{opacity:.3}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media (hover:hover){.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-yellow-600:hover{color:var(--color-yellow-600)}.hover\:text-yellow-700:hover{color:var(--color-yellow-700)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:w-auto{width:auto}.sm\:flex-row{flex-direction:row}:where(.sm\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*0)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*0)*calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.sm\:p-4{padding:calc(var(--spacing)*4)}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}@media (min-width:48rem){.md\:flex-row{flex-direction:row}.md\:p-6{padding:calc(var(--spacing)*6)}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:relative{position:relative}.lg\:hidden{display:none}.lg\:w-1\/3{width:33.3333%}.lg\:translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:transform-none{transform:none}.lg\:p-8{padding:calc(var(--spacing)*8)}}@media (min-width:80rem){.xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}}:root{color-scheme:light dark;color:#213547;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--four-geeks-blue:#0097cf;--soft-blue:#f3fafd;--gray-text:#6883b4;--learnpack-blue:#02a9ea;--learnpack-light-blue:#c7f3fd;font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;font-weight:400;line-height:1.5}a{color:var(--learnpack-blue);-webkit-text-decoration:inherit;text-decoration:inherit;font-weight:500}a:hover{color:var(--learnpack-blue)}body{background-color:#f4f9fe;min-height:100vh;margin:0}.bg-learnpack-blue{background-color:var(--soft-blue)}.bg-learnpack{background-color:var(--learnpack-blue)}.bg-gray-blue{background-color:#e7f1ff}.index-circle{width:30px;height:30px;color:var(--four-geeks-blue);background-color:var(--soft-blue);border-radius:50%;justify-content:center;align-items:center;font-size:16px;font-weight:600;display:flex}.text-gray{color:var(--gray-text)}.scrollbar-hide{scrollbar-width:none!important}.loader{justify-content:center;align-items:center;gap:10px;animation:1s linear infinite glowing;display:flex}@keyframes spin{to{transform:rotate(360deg)}}.loader-icon{justify-content:center;align-items:center;width:40px;height:40px;display:flex;position:relative}@keyframes glowing{0%{opacity:.6}to{opacity:1}}.blue-on-hover:hover svg,.blue-on-hover:hover svg path{fill:var(--learnpack-blue)}.border-learnpack-blue{border-color:var(--learnpack-blue)}.red-ball{background-color:#eb5757;border:2px solid #fff;border-radius:50%;width:16px;height:16px;position:absolute;top:-10px;left:10px}.fluid-svg>svg{width:100%;height:100%}.outline-blue:focus{outline:1px solid #51b3e5}.border-C8DBFC{border-color:#c8dbfc}.markdown-renderer ul{padding-left:0;list-style-type:disc;list-style-position:inside}.markdown-renderer ol{padding-left:0;list-style-type:decimal;list-style-position:inside}.appear{transform-origin:top;opacity:0;animation:.5s ease-in-out forwards appear;transform:scaleY(0)translateY(20px)}@keyframes appear{0%{opacity:0;transform:scaleY(0)translateY(20px)}to{opacity:1;transform:scaleY(1)translateY(0)}}.bg-heavy-blue{background-color:#9fbdf0}.border-heavy-blue{border-color:#9fbdf0}.border-heavy-blue:focus{outline:1px solid #9fbdf0}.cloudy{opacity:0;transition:opacity .5s ease-in-out;animation:.5s ease-in-out forwards fadeIn}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.yellow-ball{background-color:#08f700;border-radius:50%;width:16px;height:16px;position:absolute;top:-10px;left:10px}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}
|