, value: number): Html => {
+ const isComplete = value === 100
+ return h.div(
+ [h.Class("w-full max-w-xs")],
+ [
+ h.div(
+ [h.Class("h-1.5 w-full overflow-hidden rounded-full bg-muted")],
+ [
+ h.div(
+ [
+ h.Class(
+ cn(
+ "relative h-full rounded-full transition-all duration-500 ease-out",
+ isComplete ? "bg-success" : "bg-primary",
+ ),
+ ),
+ h.Attribute("style", `width: ${value}%;`),
+ ],
+ isComplete
+ ? []
+ : [
+ h.div(
+ [h.Class("absolute inset-0 overflow-hidden"), h.Attribute("aria-hidden", "true")],
+ [
+ h.div(
+ [
+ h.Class(
+ "absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent animate-[progress-shimmer_1.5s_ease-in-out_infinite]",
+ ),
+ ],
+ [],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ )
+}
+
+const streamingClass = cn(
+ "w-full",
+ "[&_p]:my-0",
+ "[&_blockquote]:relative [&_blockquote]:my-1 [&_blockquote]:pl-4 [&_blockquote]:italic",
+ "[&_pre]:my-2 [&_pre]:rounded [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-sm [&_pre]:overflow-x-auto",
+ "[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-sm",
+ "[&_pre_code]:bg-transparent [&_pre_code]:p-0",
+ "[&_ul]:my-0 [&_ol]:my-0",
+ "[&_li]:my-0.5 [&_li]:ml-4",
+ "[&_strong]:font-bold",
+ "[&_h1]:mt-4 [&_h1]:mb-2 [&_h1]:text-2xl [&_h1]:font-semibold [&_h1]:tracking-tight [&_h1:first-child]:mt-0",
+ "[&_h2]:mt-3 [&_h2]:mb-1.5 [&_h2]:text-xl [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2:first-child]:mt-0",
+ "[&_h3]:mt-2 [&_h3]:mb-1 [&_h3]:text-lg [&_h3]:font-semibold [&_h3]:tracking-tight [&_h3:first-child]:mt-0",
+ "[&_table]:my-2 [&_table]:w-full [&_table]:border-collapse [&_table]:text-sm",
+ "[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-3 [&_th]:py-2 [&_th]:text-left [&_th]:font-medium",
+ "[&_td]:border [&_td]:border-border [&_td]:px-3 [&_td]:py-2",
+ undefined,
+)
+
+/** `StreamingMarkdown`'s HTML: remend closes what the stream left open, marked renders it. */
+const streamingHtml = (text: string) =>
+ marked
+ .parse(remend(text || ""), { async: false, gfm: true, breaks: false })
+ .replace(/\s*<\/p>/g, "")
+ .replace(/>\s+<")
+ .trim()
+
+/** `StreamingMarkdown` with `isAnimating`: the text so far and a blinking cursor. */
+const streamingView = (h: HtmlBuilder, text: string): Html =>
+ h.div(
+ [h.Class(streamingClass), h.Attribute("aria-live", "polite"), h.Attribute("aria-busy", "true")],
+ [
+ h.span([h.InnerHTML(streamingHtml(text))], []),
+ h.span(
+ [
+ h.Class("ml-0.5 text-primary animate-[ai-cursor-blink_1s_ease-in-out_infinite]"),
+ h.Attribute("aria-hidden", "true"),
+ ],
+ ["|"],
+ ),
+ ],
+ )
+
+/** `getMessageLiveErrorCopy`. */
+const errorCopy = (error: string) =>
+ error === ACTOR_SERVICE_ERROR_UI_MESSAGE
+ ? { title: ACTOR_SERVICE_ERROR_UI_TITLE, body: ACTOR_SERVICE_ERROR_UI_MESSAGE }
+ : { title: "Something went wrong", body: error }
+
+/** `ErrorCard`. */
+const errorView = (h: HtmlBuilder, error: string): Html => {
+ const copy = errorCopy(error)
+ return h.div(
+ [
+ h.Class(
+ "rounded-lg border border-danger/20 bg-danger/5 p-4 animate-[error-enter_0.3s_var(--ease-out-cubic)_forwards]",
+ ),
+ h.Attribute("role", "alert"),
+ ],
+ [
+ h.div(
+ [h.Class("flex items-start gap-3")],
+ [
+ h.div(
+ [h.Class("rounded-full bg-danger/10 p-2 animate-[error-icon-bounce_0.4s_var(--ease-out-cubic)_0.3s]")],
+ [IconWarning(h, { className: "size-5 text-danger", attributes: { "aria-hidden": "true" } })],
+ ),
+ h.div(
+ [h.Class("flex-1 space-y-1")],
+ [
+ h.p([h.Class("font-medium text-danger text-sm")], [copy.title]),
+ h.p([h.Class("text-muted-fg text-sm")], [copy.body]),
+ ],
+ ),
+ ],
+ ),
+ ],
+ )
+}
+
+/** `MessageLive.Provider` + `Root` with `Progress`, `Steps`, `Text` and `Error`. */
+export const liveView = (h: HtmlBuilder, state: LiveActorState, loading: LiveLoading): Html => {
+ const isWaitingForContent =
+ state.status === "idle" || (state.status === "active" && !state.text && state.stepCount === 0)
+ if (isWaitingForContent) return loadingView(h, loading)
+ return h.div(
+ [h.Class(cn("space-y-2", undefined))],
+ [
+ state.status === "active" && state.progress !== null ? progressView(h, state.progress) : h.empty,
+ state.text ? (state.isStreaming ? streamingView(h, state.text) : markdownView(h, state.text)) : h.empty,
+ state.status === "failed" && state.error ? errorView(h, state.error) : h.empty,
+ ],
+ )
+}
diff --git a/apps/web-foldkit/src/chat/embeds/types.ts b/apps/web-foldkit/src/chat/embeds/types.ts
new file mode 100644
index 000000000..cc9e2e6c6
--- /dev/null
+++ b/apps/web-foldkit/src/chat/embeds/types.ts
@@ -0,0 +1,6 @@
+import type { Message } from "@hazel/domain/models"
+
+/** Element type of `Message.Type["embeds"]`, as in `message-embeds.tsx`. */
+export type EmbedType = NonNullable[number]
+export type EmbedBadge = NonNullable
+export type EmbedField = NonNullable[number]
diff --git a/apps/web-foldkit/src/chat/embeds/url-embeds.ts b/apps/web-foldkit/src/chat/embeds/url-embeds.ts
new file mode 100644
index 000000000..a25b0f618
--- /dev/null
+++ b/apps/web-foldkit/src/chat/embeds/url-embeds.ts
@@ -0,0 +1,360 @@
+import type { Html, HtmlBuilder } from "foldkit/html"
+import { IconHeart } from "../../icons"
+import type { TweetView, Unfurl } from "../unfurl"
+
+/**
+ * `MessageContent.Embeds`' URL embeds: `TweetEmbed`, `YoutubeEmbed`, `GifEmbed` and `LinkPreview`
+ * (`components/tweet-embed.tsx`, `youtube-embed.tsx`, `gif-embed.tsx`, `link-preview.tsx`).
+ */
+
+// MARK: Link preview
+
+const hostOf = (url: string) => (URL.canParse(url) ? new URL(url).host : "")
+
+/** `LinkPreview`: nothing once the fetch failed, a "Loading preview…" card until it answers. */
+export const linkPreviewView = (h: HtmlBuilder, url: string, unfurl: Unfurl | null): Html => {
+ if (unfurl === null || unfurl._tag === "Failed" || unfurl._tag === "LoadedTweet") return h.empty
+ const og = unfurl._tag === "LoadedLinkPreview" ? unfurl.preview : null
+ const href = og?.url || url
+ const host = hostOf(href)
+ const imageUrl = og?.image?.url
+ return h.a(
+ [
+ h.Href(href),
+ h.Attribute("target", "_blank"),
+ h.Attribute("rel", "noopener noreferrer"),
+ h.Class(
+ "mt-2 block max-w-sm overflow-hidden rounded-lg border pressed:border-fg/15 bg-muted/40 pressed:bg-muted hover:border-fg/15 hover:bg-muted",
+ ),
+ ],
+ [
+ imageUrl
+ ? h.div(
+ [h.Class("aspect-video w-full overflow-hidden bg-muted")],
+ [h.img([h.Attribute("src", imageUrl), h.Attribute("alt", ""), h.Class("h-full w-full object-cover")])],
+ )
+ : h.empty,
+ h.div(
+ [h.Class("flex items-start border-t p-3")],
+ [
+ h.div(
+ [h.Class("min-w-0")],
+ [
+ h.div([h.Class("truncate font-semibold text-sm")], [og?.title || host || url]),
+ og?.description
+ ? h.div([h.Class("mt-0.5 line-clamp-2 text-[12px] text-fg/70")], [og.description])
+ : h.empty,
+ h.div([h.Class("mt-1 text-[11px] text-primary-subtle-fg")], [host]),
+ ],
+ ),
+ ],
+ ),
+ unfurl._tag === "Loading"
+ ? h.div([h.Class("px-3 pb-3 text-[11px] text-muted-fg")], ["Loading preview…"])
+ : h.empty,
+ ],
+ )
+}
+
+// MARK: YouTube
+
+/** `YoutubeEmbed`: the player iframe, starting at the URL's `t` when it has one. */
+export const youtubeView = (h: HtmlBuilder, embedUrl: string): Html =>
+ h.div(
+ [
+ h.Class(
+ "mt-2 w-full max-w-xl overflow-hidden rounded-lg border border-fg/15 pressed:border-fg/15 bg-muted/40 pressed:bg-muted hover:border-fg/15 hover:bg-muted",
+ ),
+ ],
+ [
+ h.div(
+ [h.Class("relative aspect-video w-full")],
+ [
+ h.iframe([
+ h.Attribute("src", embedUrl),
+ h.Attribute("title", "YouTube video player"),
+ h.Attribute(
+ "allow",
+ "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
+ ),
+ h.Attribute("allowfullscreen", ""),
+ h.Class("absolute inset-0 size-full"),
+ ]),
+ ],
+ ),
+ ],
+ )
+
+// MARK: GIF
+
+/** `GifEmbed`: the GIF opens the image viewer; the attribution links to the provider. */
+export const gifView = (h: HtmlBuilder, mediaUrl: string, isKlipy: boolean, onOpen: M | null): Html =>
+ h.div(
+ [h.Class("mt-1")],
+ [
+ h.button(
+ [h.Attribute("type", "button"), h.Class("cursor-pointer"), ...(onOpen === null ? [] : [h.OnClick(onOpen)])],
+ [
+ h.img([
+ h.Attribute("src", mediaUrl),
+ h.Attribute("alt", "GIF"),
+ h.Class("max-h-[300px] max-w-sm rounded-md transition-opacity hover:opacity-90"),
+ h.Attribute("loading", "lazy"),
+ h.Attribute("draggable", "false"),
+ ]),
+ ],
+ ),
+ h.a(
+ [
+ h.Href(isKlipy ? "https://klipy.com" : "https://giphy.com"),
+ h.Attribute("target", "_blank"),
+ h.Attribute("rel", "noopener noreferrer"),
+ h.Class("mt-0.5 block text-[10px] text-muted-fg hover:text-fg/60"),
+ ],
+ ["via ", isKlipy ? "KLIPY" : "GIPHY"],
+ ),
+ ],
+ )
+
+// MARK: Tweet
+
+const tweetCard =
+ "mt-2 flex max-w-sm flex-col gap-2 rounded-lg border border-fg/15 bg-muted/40 p-4"
+
+const tweetSkeleton = (h: HtmlBuilder): Html =>
+ h.div(
+ [h.Class(tweetCard)],
+ [
+ h.div(
+ [h.Class("flex flex-row gap-2")],
+ [
+ h.div([h.Class("size-10 shrink-0 animate-pulse rounded-full bg-muted")], []),
+ h.div([h.Class("h-10 w-full animate-pulse rounded bg-muted")], []),
+ ],
+ ),
+ h.div([h.Class("h-20 w-full animate-pulse rounded bg-muted")], []),
+ ],
+ )
+
+const tweetNotFound = (h: HtmlBuilder): Html =>
+ h.div(
+ [
+ h.Class(
+ "mt-2 flex max-w-sm flex-col items-center justify-center gap-2 rounded-lg border border-fg/15 bg-muted/40 p-4",
+ ),
+ ],
+ [h.div([h.Class("text-muted-fg text-sm")], ["Tweet not found"])],
+ )
+
+const truncate = (value: string, length: number) =>
+ value.length <= length ? value : `${value.slice(0, length - 3)}...`
+
+const externalLink = (h: HtmlBuilder, href: string, className: string | null, children: ReadonlyArray) =>
+ h.a(
+ [
+ h.Href(href),
+ h.Attribute("target", "_blank"),
+ h.Attribute("rel", "noreferrer"),
+ ...(className === null ? [] : [h.Class(className)]),
+ ],
+ [...children],
+ )
+
+const verifiedIcon = (h: HtmlBuilder): Html =>
+ h.svg(
+ [h.Attribute("aria-label", "Verified Account"), h.Attribute("viewBox", "0 0 24 24"), h.Class("ml-1 inline size-4 text-verified")],
+ [
+ h.g(
+ [h.Attribute("fill", "currentColor")],
+ [
+ h.path([
+ h.Attribute(
+ "d",
+ "M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z",
+ ),
+ ]),
+ ],
+ ),
+ ],
+ )
+
+const twitterIcon = (h: HtmlBuilder): Html =>
+ h.svg(
+ [
+ h.Attribute("stroke", "currentColor"),
+ h.Attribute("fill", "currentColor"),
+ h.Attribute("stroke-width", "0"),
+ h.Attribute("viewBox", "0 0 24 24"),
+ h.Attribute("height", "1em"),
+ h.Attribute("width", "1em"),
+ h.Attribute("xmlns", "http://www.w3.org/2000/svg"),
+ h.Class("size-5 items-start text-[#3BA9EE] transition-all hover:scale-105"),
+ ],
+ [
+ h.g(
+ [],
+ [
+ h.path([h.Attribute("fill", "none"), h.Attribute("d", "M0 0h24v24H0z")]),
+ h.path([
+ h.Attribute(
+ "d",
+ "M22.162 5.656a8.384 8.384 0 0 1-2.402.658A4.196 4.196 0 0 0 21.6 4c-.82.488-1.719.83-2.656 1.015a4.182 4.182 0 0 0-7.126 3.814 11.874 11.874 0 0 1-8.62-4.37 4.168 4.168 0 0 0-.566 2.103c0 1.45.738 2.731 1.86 3.481a4.168 4.168 0 0 1-1.894-.523v.052a4.185 4.185 0 0 0 3.355 4.101 4.21 4.21 0 0 1-1.89.072A4.185 4.185 0 0 0 7.97 16.65a8.394 8.394 0 0 1-6.191 1.732 11.83 11.83 0 0 0 6.41 1.88c7.693 0 11.9-6.373 11.9-11.9 0-.18-.005-.362-.013-.54a8.496 8.496 0 0 0 2.087-2.165z",
+ ),
+ ]),
+ ],
+ ),
+ ],
+ )
+
+const tweetHeader = (h: HtmlBuilder, tweet: TweetView): Html =>
+ h.div(
+ [h.Class("flex flex-row justify-between")],
+ [
+ h.div(
+ [h.Class("flex items-center space-x-2")],
+ [
+ externalLink(h, tweet.user.url, null, [
+ h.img([
+ h.Attribute("alt", tweet.user.screenName),
+ h.Attribute("height", "48"),
+ h.Attribute("width", "48"),
+ h.Attribute("src", tweet.user.avatarUrl),
+ h.Class("size-10 overflow-hidden rounded-full border border-fg/15"),
+ ]),
+ ]),
+ h.div(
+ [],
+ [
+ externalLink(h, tweet.user.url, "flex items-center whitespace-nowrap font-semibold text-sm hover:underline", [
+ truncate(tweet.user.name, 20),
+ tweet.user.isVerified ? verifiedIcon(h) : h.empty,
+ ]),
+ h.div(
+ [h.Class("flex items-center space-x-1")],
+ [
+ externalLink(h, tweet.user.url, "text-fg/70 text-sm hover:underline", [
+ "@",
+ truncate(tweet.user.screenName, 16),
+ ]),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ externalLink(h, tweet.url, null, [h.span([h.Class("sr-only")], ["Link to tweet"]), twitterIcon(h)]),
+ ],
+ )
+
+const tweetBody = (h: HtmlBuilder, tweet: TweetView): Html =>
+ h.div(
+ [h.Class("wrap-break-word leading-normal tracking-tighter")],
+ tweet.entities.map((entity) =>
+ entity.type === "text"
+ ? h.span([h.Class("text-fg text-sm")], [entity.text])
+ : entity.type === "media" || entity.href === null
+ ? h.empty
+ : h.a(
+ [
+ h.Href(entity.href),
+ h.Attribute("target", "_blank"),
+ h.Attribute("rel", "noopener noreferrer"),
+ h.Class("text-primary-subtle-fg text-sm hover:underline"),
+ ],
+ [h.span([], [entity.text])],
+ ),
+ ),
+ )
+
+const tweetMedia = (h: HtmlBuilder, tweet: TweetView, toOpenPhoto: ((index: number) => M) | null): Html => {
+ if (tweet.video === null && tweet.photos === null) return h.empty
+ return h.div(
+ [h.Class("flex flex-1 items-center justify-center")],
+ [
+ tweet.video !== null && tweet.video.src !== null
+ ? h.video(
+ [
+ h.Attribute("poster", tweet.video.poster),
+ h.Attribute("autoplay", ""),
+ h.Attribute("loop", ""),
+ h.Attribute("playsinline", ""),
+ h.Class("rounded-lg border border-fg/15 shadow-sm"),
+ ],
+ [
+ h.source([h.Attribute("src", tweet.video.src), h.Attribute("type", "video/mp4")]),
+ "Your browser does not support the video tag.",
+ ],
+ )
+ : h.empty,
+ tweet.photos === null
+ ? h.empty
+ : h.div(
+ [h.Class("relative flex transform-gpu snap-x snap-mandatory gap-4 overflow-x-auto")],
+ [
+ h.div([h.Class("shrink-0 snap-center sm:w-2")], []),
+ ...tweet.photos.map((photo, index) =>
+ h.button(
+ [
+ h.Attribute("type", "button"),
+ h.Class("h-64 w-5/6 shrink-0 snap-center snap-always"),
+ ...(toOpenPhoto === null ? [] : [h.OnClick(toOpenPhoto(index))]),
+ ],
+ [
+ h.img([
+ h.Attribute("src", photo.url),
+ h.Attribute("width", String(photo.width)),
+ h.Attribute("height", String(photo.height)),
+ h.Attribute("alt", tweet.text),
+ h.Class(
+ "size-full rounded-lg border border-fg/15 object-cover shadow-sm transition-opacity hover:opacity-90",
+ ),
+ ]),
+ ],
+ ),
+ ),
+ h.div([h.Class("shrink-0 snap-center sm:w-2")], []),
+ ],
+ ),
+ ],
+ )
+}
+
+const metric = (h: HtmlBuilder, count: number, label: string, icon: Html): Html =>
+ h.div(
+ [h.Class("flex items-center gap-1")],
+ [icon, h.span([h.Class("font-semibold")], [count.toLocaleString()]), h.span([], [label])],
+ )
+
+const tweetMetrics = (h: HtmlBuilder, tweet: TweetView): Html => {
+ const { replyCount, retweetCount, favoriteCount } = tweet
+ if (favoriteCount === null && retweetCount === null && replyCount === null) return h.empty
+ return h.div(
+ [h.Class("flex items-center gap-4 text-fg/70 text-xs")],
+ [
+ replyCount !== null && replyCount > 0 ? metric(h, replyCount, "Replies", h.empty) : h.empty,
+ retweetCount !== null && retweetCount > 0 ? metric(h, retweetCount, "Retweets", h.empty) : h.empty,
+ favoriteCount !== null && favoriteCount > 0
+ ? metric(h, favoriteCount, favoriteCount > 1 ? "Likes" : "Like", IconHeart(h, { className: "size-3 text-primary" }))
+ : h.empty,
+ ],
+ )
+}
+
+/** `TweetEmbed`: a skeleton while loading, "Tweet not found" when the fetch failed. */
+export const tweetView = (
+ h: HtmlBuilder,
+ unfurl: Unfurl | null,
+ toOpenPhoto: ((index: number) => M) | null,
+): Html => {
+ if (unfurl === null || unfurl._tag === "Loading") return tweetSkeleton(h)
+ if (unfurl._tag !== "LoadedTweet") return tweetNotFound(h)
+ const { tweet } = unfurl
+ return h.div(
+ [
+ h.Class(
+ "mt-2 flex max-w-sm flex-col gap-2 overflow-hidden rounded-lg border border-fg/15 pressed:border-fg/15 bg-muted/40 pressed:bg-muted p-4 hover:border-fg/15 hover:bg-muted",
+ ),
+ ],
+ [tweetHeader(h, tweet), tweetBody(h, tweet), tweetMedia(h, tweet, toOpenPhoto), tweetMetrics(h, tweet)],
+ )
+}
diff --git a/apps/web-foldkit/src/chat/file-icons.ts b/apps/web-foldkit/src/chat/file-icons.ts
new file mode 100644
index 000000000..62a5ea98b
--- /dev/null
+++ b/apps/web-foldkit/src/chat/file-icons.ts
@@ -0,0 +1,1506 @@
+// Generated by scripts/generate-file-icons.ts from @untitledui/file-icons. Do not edit.
+import type { Html, HtmlBuilder } from "foldkit/html"
+
+type Tag = "svg" | "path" | "rect" | "g" | "defs" | "clipPath"
+type Tree = readonly [Tag, Readonly>, ReadonlyArray]
+
+export const SUPPORTED_FILE_TYPES: ReadonlyArray = [
+ "audio",
+ "code",
+ "document",
+ "empty",
+ "folder",
+ "image",
+ "img",
+ "spreadsheets",
+ "video",
+ "video-01",
+ "video-02",
+ "aep",
+ "ai",
+ "avi",
+ "css",
+ "csv",
+ "dmg",
+ "doc",
+ "docx",
+ "eps",
+ "exe",
+ "fig",
+ "gif",
+ "html",
+ "indd",
+ "java",
+ "jpeg",
+ "jpg",
+ "js",
+ "json",
+ "mkv",
+ "mp3",
+ "mp4",
+ "mpeg",
+ "pdf",
+ "pdf-simple",
+ "png",
+ "ppt",
+ "pptx",
+ "psd",
+ "rar",
+ "rss",
+ "sql",
+ "svg",
+ "tiff",
+ "txt",
+ "wav",
+ "webp",
+ "xls",
+ "xlsx",
+ "xml",
+ "zip",
+]
+
+/** The FileIcon `default` variant, light theme, keyed by type. */
+const icons: Readonly> = {
+ audio: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "path",
+ {
+ stroke: "#DD2590",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "M16.85 28.5v-8.733c0-.362 0-.542.066-.689a.75.75 0 0 1 .269-.317c.133-.089.312-.119.668-.178l6.6-1.1c.48-.08.72-.12.908-.05a.75.75 0 0 1 .39.33c.099.172.099.416.099.904V27m-9 1.5a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0m9-1.5a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0",
+ },
+ [],
+ ],
+ ],
+ ],
+ code: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "path",
+ {
+ stroke: "#444CE7",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "M23.75 27.75 27.5 24l-3.75-3.75m-7.5 0L12.5 24l3.75 3.75m5.25-10.5-3 13.5",
+ },
+ [],
+ ],
+ ],
+ ],
+ document: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "path",
+ {
+ stroke: "#155EEF",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "M11.9 19.5h16.2m-16.2 3.6h16.2m-16.2 3.6h16.2m-16.2 3.6h12.6",
+ },
+ [],
+ ],
+ ],
+ ],
+ empty: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ],
+ ],
+ folder: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "path",
+ {
+ stroke: "#7F56D9",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "m20.75 20.25-.837-1.673c-.24-.482-.36-.723-.54-.899a1.5 1.5 0 0 0-.56-.346c-.239-.082-.508-.082-1.046-.082H14.9c-.84 0-1.26 0-1.581.163a1.5 1.5 0 0 0-.655.656c-.164.32-.164.74-.164 1.581v.6m0 0h11.4c1.26 0 1.89 0 2.372.245.423.216.767.56.983.983.245.482.245 1.112.245 2.372v3.3c0 1.26 0 1.89-.245 2.372-.216.423-.56.767-.983.983-.482.245-1.112.245-2.372.245h-7.8c-1.26 0-1.89 0-2.372-.245a2.25 2.25 0 0 1-.983-.983c-.245-.482-.245-1.112-.245-2.372z",
+ },
+ [],
+ ],
+ ],
+ ],
+ image: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "path",
+ {
+ stroke: "#7F56D9",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "M25.25 30.75h.758c.728 0 1.092 0 1.293-.152a.75.75 0 0 0 .296-.553c.015-.252-.187-.555-.59-1.16l-2.259-3.387c-.333-.501-.5-.751-.71-.839a.75.75 0 0 0-.575 0c-.21.088-.378.338-.712.839l-.558.837m3.057 4.415-5.763-8.325c-.332-.479-.498-.718-.705-.802a.75.75 0 0 0-.564 0c-.207.084-.373.323-.705.802l-4.46 6.442c-.422.61-.633.915-.62 1.168a.75.75 0 0 0 .293.56c.201.155.572.155 1.314.155zm1.5-11.25a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0",
+ },
+ [],
+ ],
+ ],
+ ],
+ img: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M6.39 22.727V30H4.851v-7.273zm1.265 0H9.55l2.003 4.887h.085l2.003-4.887h1.896V30h-1.491v-4.734h-.06l-1.883 4.699H11.09l-1.883-4.716h-.06V30H7.655zm13.972 2.351a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.543-.074q-.558 0-.98.277-.42.277-.654.806-.234.526-.234 1.285 0 .76.23 1.293.233.533.654.813.423.277.998.277.522 0 .892-.184.373-.188.568-.53.198-.34.198-.805l.313.046h-1.875v-1.158h3.043v.916q0 .96-.405 1.648-.405.686-1.114 1.058-.71.37-1.627.37-1.022 0-1.797-.452a3.13 3.13 0 0 1-1.207-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.26-.696.724-1.179.465-.483 1.083-.735a3.5 3.5 0 0 1 1.34-.252q.617 0 1.15.18.533.179.944.505.415.327.678.778.264.447.338.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ spreadsheets: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "path",
+ {
+ stroke: "#079455",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "M11.9 24.9h16.2m-16.2 0v-3.6a1.8 1.8 0 0 1 1.8-1.8h3.6m-5.4 5.4v3.6a1.8 1.8 0 0 0 1.8 1.8h3.6m10.8-5.4v3.6a1.8 1.8 0 0 1-1.8 1.8h-9m10.8-5.4v-3.6a1.8 1.8 0 0 0-1.8-1.8h-9m0 0v10.8",
+ },
+ [],
+ ],
+ ],
+ ],
+ video: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "g",
+ { "clip-path": "url(#video_svg__a)" },
+ [
+ [
+ "path",
+ {
+ stroke: "#155EEF",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "M12.5 24h15m-15-3.75h3.75m7.5 0h3.75m-15 7.5h3.75m7.5 0h3.75M16.25 31.5v-15m7.5 15v-15m-7.65 15h7.8c1.26 0 1.89 0 2.372-.245.423-.216.767-.56.983-.983.245-.482.245-1.112.245-2.372v-7.8c0-1.26 0-1.89-.245-2.372a2.25 2.25 0 0 0-.983-.983C25.79 16.5 25.16 16.5 23.9 16.5h-7.8c-1.26 0-1.89 0-2.372.245-.423.216-.767.56-.983.983-.245.482-.245 1.112-.245 2.372v7.8c0 1.26 0 1.89.245 2.372.216.423.56.767.984.983.48.245 1.11.245 2.371.245",
+ },
+ [],
+ ],
+ ],
+ ],
+ [
+ "defs",
+ {},
+ [
+ [
+ "clipPath",
+ { id: "video_svg__a" },
+ [["path", { fill: "#fff", d: "M11 15h18v18H11z" }, []]],
+ ],
+ ],
+ ],
+ ],
+ ],
+ "video-01": [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "g",
+ { "clip-path": "url(#video-01_svg__a)" },
+ [
+ [
+ "path",
+ {
+ stroke: "#155EEF",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ d: "M12.5 24h15m-15-3.75h3.75m7.5 0h3.75m-15 7.5h3.75m7.5 0h3.75M16.25 31.5v-15m7.5 15v-15m-7.65 15h7.8c1.26 0 1.89 0 2.372-.245.423-.216.767-.56.983-.983.245-.482.245-1.112.245-2.372v-7.8c0-1.26 0-1.89-.245-2.372a2.25 2.25 0 0 0-.983-.983C25.79 16.5 25.16 16.5 23.9 16.5h-7.8c-1.26 0-1.89 0-2.372.245-.423.216-.767.56-.983.983-.245.482-.245 1.112-.245 2.372v7.8c0 1.26 0 1.89.245 2.372.216.423.56.767.984.983.48.245 1.11.245 2.371.245",
+ },
+ [],
+ ],
+ ],
+ ],
+ [
+ "defs",
+ {},
+ [
+ [
+ "clipPath",
+ { id: "video-01_svg__a" },
+ [["path", { fill: "#fff", d: "M11 15h18v18H11z" }, []]],
+ ],
+ ],
+ ],
+ ],
+ ],
+ "video-02": [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "g",
+ {
+ stroke: "#155EEF",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ "stroke-width": "1.5",
+ "clip-path": "url(#video-02_svg__a)",
+ },
+ [
+ ["path", { d: "M20 31.5a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15" }, []],
+ [
+ "path",
+ {
+ d: "M18.125 21.724c0-.358 0-.537.075-.637a.38.38 0 0 1 .273-.15c.125-.008.275.089.576.282l3.54 2.276c.262.168.393.252.438.359a.37.37 0 0 1 0 .292c-.045.107-.176.19-.437.359l-3.54 2.276c-.302.193-.452.29-.577.281a.38.38 0 0 1-.273-.15c-.075-.1-.075-.278-.075-.636z",
+ },
+ [],
+ ],
+ ],
+ ],
+ [
+ "defs",
+ {},
+ [
+ [
+ "clipPath",
+ { id: "video-02_svg__a" },
+ [["path", { fill: "#fff", d: "M11 15h18v18H11z" }, []]],
+ ],
+ ],
+ ],
+ ],
+ ],
+ aep: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "27", height: "16", x: "1", y: "18", fill: "#6938EF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M6.347 30H4.699l2.51-7.273h1.982L11.698 30H10.05l-1.82-5.61h-.057zm-.103-2.859h3.892v1.2H6.244zM12.574 30v-7.273h4.9v1.268h-3.363v1.733h3.11v1.268h-3.11v1.736h3.377V30zm6.123 0v-7.273h2.869q.827 0 1.41.316.582.314.887.87.31.555.31 1.279t-.313 1.278-.906.863q-.59.309-1.427.309h-1.829V26.41h1.58q.444 0 .732-.153.29-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153h-1.037V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ ai: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "17", height: "16", x: "1", y: "18", fill: "#E04F16", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M6.244 30H4.596l2.511-7.273h1.982L11.596 30H9.948l-1.822-5.61H8.07zm-.103-2.859h3.892v1.2H6.141zm7.868-4.414V30H12.47v-7.273z",
+ },
+ [],
+ ],
+ ],
+ ],
+ avi: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "23", height: "16", x: "1", y: "18", fill: "#155EEF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M5.958 30H4.31l2.51-7.273h1.982L11.31 30H9.662L7.84 24.39h-.057zm-.103-2.859h3.892v1.2H5.855zm6.736-4.414 1.757 5.526h.068l1.761-5.526h1.705L15.375 30h-1.982l-2.51-7.273zm7.704 0V30h-1.538v-7.273z",
+ },
+ [],
+ ],
+ ],
+ ],
+ css: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "27", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M11.237 25.273H9.68a1.5 1.5 0 0 0-.174-.536 1.4 1.4 0 0 0-.337-.405 1.5 1.5 0 0 0-.476-.255 1.8 1.8 0 0 0-.579-.09q-.564 0-.984.282-.419.276-.65.81-.23.528-.23 1.285 0 .777.23 1.306.236.53.654.8t.97.27q.308 0 .571-.082.267-.082.472-.238a1.4 1.4 0 0 0 .341-.387q.14-.228.192-.519l1.556.007q-.06.501-.302.966a2.9 2.9 0 0 1-.643.828 3 3 0 0 1-.959.575q-.554.21-1.253.21-.975 0-1.74-.44a3.13 3.13 0 0 1-1.208-1.276q-.44-.834-.44-2.02 0-1.19.447-2.024t1.215-1.272a3.4 3.4 0 0 1 1.726-.44q.631 0 1.171.177.544.179.963.519.419.337.682.827.265.49.34 1.122m5.047-.454a.9.9 0 0 0-.365-.668q-.324-.238-.878-.238-.375 0-.635.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.9.9 0 0 0 .252.253q.16.103.37.18.209.075.447.129l.653.156q.476.106.874.284.398.177.689.437.291.259.45.61.164.353.168.807-.004.667-.341 1.157-.334.487-.966.757-.63.266-1.517.266-.88 0-1.534-.27a2.25 2.25 0 0 1-1.015-.799q-.363-.533-.38-1.317h1.488q.024.366.21.61.188.242.5.366.315.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.146-.412a1.1 1.1 0 0 0-.419-.284 4 4 0 0 0-.671-.213l-.792-.199q-.92-.224-1.452-.7-.534-.475-.53-1.282-.003-.66.352-1.154.36-.493.984-.77t1.42-.277q.81 0 1.414.277.607.276.944.77.338.494.348 1.144zm6.553 0a.9.9 0 0 0-.366-.668q-.322-.238-.877-.238-.376 0-.635.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.85.85 0 0 0 .252.253q.16.103.37.18.209.075.447.129l.653.156q.475.106.874.284.397.177.688.437.292.259.451.61.165.353.167.807-.003.667-.34 1.157-.334.487-.966.757-.63.266-1.517.266-.88 0-1.534-.27a2.25 2.25 0 0 1-1.015-.799q-.363-.533-.38-1.317h1.487q.025.366.21.61.188.242.5.366.317.12.714.12.39 0 .679-.113a1.04 1.04 0 0 0 .45-.316.73.73 0 0 0 .16-.465q0-.244-.145-.412a1.1 1.1 0 0 0-.42-.284 4 4 0 0 0-.67-.213l-.792-.199q-.92-.224-1.453-.7-.532-.475-.529-1.282-.003-.66.352-1.154.359-.493.983-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.945.77t.348 1.144z",
+ },
+ [],
+ ],
+ ],
+ ],
+ csv: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "28", height: "16", x: "1", y: "18", fill: "#079455", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M11.273 25.273H9.717a1.5 1.5 0 0 0-.174-.536 1.4 1.4 0 0 0-.337-.405 1.5 1.5 0 0 0-.476-.255 1.8 1.8 0 0 0-.579-.09q-.564 0-.983.282-.42.276-.65.81-.231.528-.231 1.285 0 .777.23 1.306.236.53.654.8.42.27.97.27.309 0 .571-.082.267-.082.473-.238a1.4 1.4 0 0 0 .34-.387q.14-.228.192-.519l1.556.007q-.06.501-.302.966a2.9 2.9 0 0 1-.643.828 3 3 0 0 1-.959.575q-.554.21-1.253.21-.973 0-1.74-.44a3.13 3.13 0 0 1-1.208-1.276q-.44-.834-.44-2.02 0-1.19.447-2.024.448-.835 1.215-1.272a3.4 3.4 0 0 1 1.726-.44q.632 0 1.172.177.543.179.962.519.42.337.682.827.265.49.34 1.122m5.048-.454a.9.9 0 0 0-.366-.668q-.324-.238-.877-.238-.377 0-.636.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.85.85 0 0 0 .252.253q.16.103.37.18.21.075.447.129l.653.156q.477.106.874.284.398.177.689.437.291.259.45.61.164.353.168.807-.004.667-.341 1.157-.335.487-.966.757-.63.266-1.516.266-.882 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.362-.533-.38-1.317h1.488q.024.366.21.61.188.242.5.366.316.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.146-.412a1.1 1.1 0 0 0-.419-.284 4 4 0 0 0-.67-.213l-.793-.199q-.92-.224-1.452-.7-.533-.475-.53-1.282-.003-.66.352-1.154.36-.493.984-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.944.77.338.494.348 1.144zm3.92-2.092L22 28.253h.067l1.762-5.526h1.704L23.026 30h-1.982l-2.51-7.273z",
+ },
+ [],
+ ],
+ ],
+ ],
+ dmg: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "31", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.694 30H5.116v-7.273h2.599q1.097 0 1.89.437.79.433 1.217 1.246.43.814.43 1.947 0 1.136-.43 1.953a2.95 2.95 0 0 1-1.225 1.253Q8.801 30 7.694 30m-1.04-1.317h.976q.682 0 1.147-.242.468-.244.703-.756.238-.516.238-1.328 0-.807-.238-1.318a1.54 1.54 0 0 0-.7-.753q-.465-.24-1.147-.241h-.98zm5.737-5.956h1.896l2.003 4.887h.085l2.003-4.887h1.896V30h-1.491v-4.734h-.06l-1.883 4.699h-1.015l-1.882-4.716h-.06V30H12.39zm13.973 2.351a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.544-.074q-.557 0-.98.277-.42.277-.653.806-.235.526-.235 1.285 0 .76.231 1.293t.654.813q.422.277.998.277.522 0 .89-.184.375-.188.569-.53.2-.34.199-.805l.312.046H24.95v-1.158h3.044v.916q0 .96-.405 1.648a2.76 2.76 0 0 1-1.115 1.058q-.71.37-1.627.37-1.021 0-1.797-.452a3.13 3.13 0 0 1-1.207-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.259-.696.725-1.179a3.1 3.1 0 0 1 1.083-.735 3.5 3.5 0 0 1 1.338-.252q.618 0 1.151.18.533.179.945.505.415.327.678.778.263.447.337.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ doc: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "29", height: "16", x: "1", y: "18", fill: "#155EEF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.402 30H4.824v-7.273h2.599q1.096 0 1.89.437.79.433 1.217 1.246.43.814.43 1.947 0 1.136-.43 1.953a2.95 2.95 0 0 1-1.225 1.253Q8.509 30 7.402 30m-1.04-1.317h.976q.682 0 1.147-.242.468-.244.703-.756.237-.516.238-1.328 0-.807-.238-1.318a1.54 1.54 0 0 0-.7-.753q-.465-.24-1.147-.241h-.98zm12.42-2.32q0 1.19-.45 2.025a3.13 3.13 0 0 1-1.222 1.275 3.45 3.45 0 0 1-1.733.436 3.44 3.44 0 0 1-1.74-.44 3.14 3.14 0 0 1-1.219-1.275q-.447-.834-.447-2.02 0-1.19.447-2.024a3.1 3.1 0 0 1 1.219-1.272 3.44 3.44 0 0 1 1.74-.44q.962 0 1.733.44.774.437 1.221 1.271.45.835.451 2.025m-1.559 0q0-.77-.23-1.3-.228-.529-.643-.802a1.73 1.73 0 0 0-.973-.273 1.73 1.73 0 0 0-.973.273q-.416.274-.647.803-.227.53-.227 1.3t.227 1.3q.231.529.647.802.415.273.973.273.557 0 .973-.273t.642-.803q.231-.528.231-1.3m9.115-1.09h-1.555a1.5 1.5 0 0 0-.174-.536 1.4 1.4 0 0 0-.338-.405 1.5 1.5 0 0 0-.476-.255 1.8 1.8 0 0 0-.578-.09q-.566 0-.984.282-.42.276-.65.81-.23.528-.23 1.285 0 .777.23 1.306.234.53.654.8.419.27.969.27.308 0 .572-.082.266-.082.472-.238.205-.16.34-.387.14-.228.193-.519l1.555.007q-.06.501-.302.966a2.9 2.9 0 0 1-.643.828 3 3 0 0 1-.958.575q-.554.21-1.254.21-.974 0-1.74-.44a3.13 3.13 0 0 1-1.207-1.276q-.44-.834-.44-2.02 0-1.19.447-2.024t1.214-1.272a3.4 3.4 0 0 1 1.726-.44q.632 0 1.172.177.543.179.962.519.42.337.682.827.267.49.341 1.122",
+ },
+ [],
+ ],
+ ],
+ ],
+ docx: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "36", height: "16", x: "1", y: "18", fill: "#155EEF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.406 30H4.827v-7.273h2.6q1.097 0 1.89.437.79.433 1.217 1.246.43.814.43 1.947 0 1.136-.43 1.953a2.95 2.95 0 0 1-1.225 1.253Q8.513 30 7.406 30m-1.04-1.317h.976q.682 0 1.147-.242.468-.244.703-.756.237-.516.238-1.328 0-.807-.238-1.318a1.54 1.54 0 0 0-.7-.753q-.465-.24-1.147-.241h-.98zm12.42-2.32q0 1.19-.451 2.025a3.13 3.13 0 0 1-1.222 1.275 3.45 3.45 0 0 1-1.732.436 3.44 3.44 0 0 1-1.74-.44 3.14 3.14 0 0 1-1.219-1.275q-.447-.834-.447-2.02 0-1.19.447-2.024a3.1 3.1 0 0 1 1.218-1.272 3.44 3.44 0 0 1 1.74-.44q.963 0 1.733.44.775.437 1.222 1.271.45.835.451 2.025m-1.559 0q0-.77-.23-1.3-.228-.529-.643-.802a1.73 1.73 0 0 0-.973-.273 1.73 1.73 0 0 0-.973.273q-.416.274-.647.803-.227.53-.227 1.3t.227 1.3q.231.529.647.802.414.273.973.273.556 0 .973-.273.415-.273.642-.803.231-.528.231-1.3m9.115-1.09h-1.555a1.5 1.5 0 0 0-.174-.536 1.4 1.4 0 0 0-.338-.405 1.5 1.5 0 0 0-.476-.255 1.8 1.8 0 0 0-.578-.09q-.566 0-.984.282-.42.276-.65.81-.23.528-.23 1.285 0 .777.23 1.306.234.53.653.8t.97.27q.308 0 .572-.082.266-.082.472-.238a1.4 1.4 0 0 0 .34-.387q.14-.228.193-.519l1.555.007q-.06.501-.302.966a2.9 2.9 0 0 1-.643.828 3 3 0 0 1-.958.575q-.555.21-1.254.21-.974 0-1.74-.44a3.13 3.13 0 0 1-1.207-1.276q-.44-.834-.44-2.02 0-1.19.447-2.024t1.214-1.272a3.4 3.4 0 0 1 1.726-.44q.632 0 1.172.177.543.179.962.519.42.337.682.827.267.49.341 1.122m2.388-2.546 1.467 2.479h.057l1.473-2.479h1.737l-2.22 3.637L33.514 30h-1.769l-1.491-2.482h-.057L28.705 30h-1.761l2.276-3.636-2.233-3.637z",
+ },
+ [],
+ ],
+ ],
+ ],
+ eps: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M5.057 30v-7.273h4.9v1.268H6.595v1.733h3.11v1.268h-3.11v1.736h3.377V30zm6.123 0v-7.273h2.87q.825 0 1.41.316.58.314.887.87.309.555.309 1.279t-.313 1.278-.905.863q-.59.309-1.428.309h-1.829V26.41h1.58q.445 0 .732-.153.291-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153h-1.036V30zm10.42-5.181a.9.9 0 0 0-.366-.668q-.323-.238-.877-.238-.377 0-.636.107a.9.9 0 0 0-.397.288.7.7 0 0 0-.135.419.6.6 0 0 0 .081.34.85.85 0 0 0 .253.253q.16.103.369.18.21.075.447.129l.654.156q.476.106.873.284.398.177.69.437.29.259.45.61.164.353.167.807-.003.667-.34 1.157-.334.487-.967.757-.628.266-1.516.266-.881 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.362-.533-.38-1.317h1.488q.025.366.21.61.188.242.5.366.316.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.145-.412a1.1 1.1 0 0 0-.42-.284 4 4 0 0 0-.67-.213l-.793-.199q-.92-.224-1.452-.7-.532-.475-.53-1.282-.002-.66.352-1.154.36-.493.984-.77.625-.277 1.42-.277.81 0 1.414.277.607.276.945.77t.348 1.144z",
+ },
+ [],
+ ],
+ ],
+ ],
+ exe: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.935 30v-7.273h4.9v1.268H6.472v1.733h3.111v1.268h-3.11v1.736H9.85V30zm7.565-7.273 1.466 2.479h.057l1.474-2.479h1.736l-2.22 3.637L17.284 30h-1.768l-1.492-2.482h-.057L12.475 30h-1.762l2.277-3.636-2.234-3.637zM18.206 30v-7.273h4.9v1.268h-3.362v1.733h3.11v1.268h-3.11v1.736h3.377V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ fig: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "23", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M5.002 30v-7.273h4.816v1.268H6.54v1.733h2.958v1.268H6.54V30zm7.388-7.273V30h-1.538v-7.273zm6.087 2.351a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.543-.074q-.558 0-.98.277-.42.277-.654.806-.234.526-.234 1.285 0 .76.23 1.293.231.533.654.813.422.277.998.277.522 0 .891-.184.373-.188.568-.53.2-.34.2-.805l.312.046h-1.875v-1.158h3.043v.916q0 .96-.405 1.648-.404.686-1.115 1.058-.71.37-1.626.37-1.023 0-1.797-.452a3.13 3.13 0 0 1-1.208-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.26-.696.725-1.179t1.083-.735a3.5 3.5 0 0 1 1.339-.252q.618 0 1.15.18.533.179.945.505.416.327.678.778.263.447.337.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ gif: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "23", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M9.825 25.078a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.544-.074q-.557 0-.98.277-.42.277-.653.806-.235.526-.235 1.285 0 .76.231 1.293t.654.813q.422.277.997.277.523 0 .892-.184.372-.188.568-.53.2-.34.199-.805l.312.046H8.411v-1.158h3.044v.916q0 .96-.405 1.648a2.76 2.76 0 0 1-1.115 1.058q-.71.37-1.627.37-1.022 0-1.797-.452a3.13 3.13 0 0 1-1.207-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.26-.696.724-1.179a3.1 3.1 0 0 1 1.084-.735 3.5 3.5 0 0 1 1.338-.252q.619 0 1.15.18.533.179.945.505.416.327.679.778.262.447.337.987zm4.322-2.35V30H12.61v-7.273zM15.412 30v-7.273h4.816v1.268H16.95v1.733h2.958v1.268H16.95V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ html: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "35", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.65 30v-7.273h1.537v3.001H9.31v-3h1.534V30H9.309v-3.004H6.187V30zm7.184-6.005v-1.268h5.973v1.268H15.58V30h-1.52v-6.005zm6.956-1.268h1.897l2.002 4.887h.086l2.003-4.887h1.896V30h-1.492v-4.734h-.06l-1.882 4.699h-1.016l-1.882-4.716h-.06V30H18.79zM27.94 30v-7.273h1.538v6.005h3.118V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ indd: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "31", height: "16", x: "1", y: "18", fill: "#BA24D5", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M6.316 22.727V30H4.78v-7.273zm7.348 0V30h-1.328l-3.164-4.577H9.12V30H7.581v-7.273h1.35l3.139 4.574h.064v-4.574zM17.513 30h-2.578v-7.273h2.6q1.095 0 1.889.437.79.433 1.218 1.246.43.814.43 1.947 0 1.136-.43 1.953a2.95 2.95 0 0 1-1.226 1.253q-.795.437-1.903.437m-1.04-1.317h.976q.682 0 1.147-.242.47-.244.703-.756.238-.516.238-1.328 0-.807-.238-1.318a1.54 1.54 0 0 0-.7-.753q-.465-.24-1.146-.241h-.98zM24.788 30H22.21v-7.273h2.6q1.096 0 1.889.437.792.433 1.218 1.246.43.814.43 1.947 0 1.136-.43 1.953a2.95 2.95 0 0 1-1.225 1.253q-.795.437-1.904.437m-1.04-1.317h.976q.682 0 1.147-.242.469-.244.704-.756.237-.516.238-1.328 0-.807-.238-1.318a1.54 1.54 0 0 0-.7-.753q-.465-.24-1.147-.241h-.98z",
+ },
+ [],
+ ],
+ ],
+ ],
+ java: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "33", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.91 22.727h1.52v5.071q0 .703-.317 1.222a2.1 2.1 0 0 1-.87.799q-.558.28-1.296.28-.657 0-1.193-.23a1.9 1.9 0 0 1-.845-.71q-.313-.48-.31-1.204H6.13q.007.287.117.493a.8.8 0 0 0 .31.313 1 1 0 0 0 .468.106q.285 0 .48-.12a.8.8 0 0 0 .301-.363q.103-.238.103-.586zM11.953 30h-1.648l2.51-7.273H14.8L17.306 30h-1.648l-1.822-5.61h-.057zm-.103-2.859h3.892v1.2H11.85zm6.736-4.414 1.758 5.526h.067l1.761-5.526h1.705L21.37 30h-1.982l-2.51-7.273zM25.04 30h-1.648l2.51-7.273h1.982L30.392 30h-1.648l-1.822-5.61h-.057zm-.103-2.859h3.892v1.2h-3.892z",
+ },
+ [],
+ ],
+ ],
+ ],
+ jpeg: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "32", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.59 22.727h1.52v5.071q0 .703-.316 1.222a2.1 2.1 0 0 1-.87.799q-.558.28-1.296.28-.657 0-1.194-.23a1.9 1.9 0 0 1-.845-.71q-.312-.48-.309-1.204h1.53q.009.287.118.493a.8.8 0 0 0 .309.313 1 1 0 0 0 .469.106q.284 0 .48-.12a.8.8 0 0 0 .301-.363q.103-.238.103-.586zM10.38 30v-7.273h2.87q.826 0 1.41.316.582.314.887.87.309.555.309 1.279t-.312 1.278-.906.863q-.59.309-1.428.309h-1.828V26.41h1.58q.444 0 .731-.153.291-.156.434-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.434-.423q-.291-.153-.738-.153h-1.037V30zm6.475 0v-7.273h4.9v1.268h-3.363v1.733h3.111v1.268h-3.11v1.736h3.377V30zM27.8 25.078a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.543-.074q-.558 0-.98.277-.42.277-.654.806-.234.526-.234 1.285 0 .76.23 1.293.232.533.654.813.422.277.998.277.522 0 .891-.184.373-.188.569-.53.198-.34.198-.805l.313.046h-1.875v-1.158h3.043v.916q0 .96-.405 1.648a2.76 2.76 0 0 1-1.115 1.058q-.71.37-1.626.37-1.022 0-1.797-.452a3.13 3.13 0 0 1-1.207-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.259-.696.724-1.179a3.1 3.1 0 0 1 1.083-.735 3.5 3.5 0 0 1 1.339-.252q.618 0 1.15.18.533.179.945.505.415.327.678.778.263.447.338.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ jpg: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.651 22.727h1.52v5.071q0 .703-.316 1.222a2.1 2.1 0 0 1-.87.799q-.557.28-1.296.28-.657 0-1.193-.23a1.9 1.9 0 0 1-.845-.71q-.313-.48-.31-1.204h1.531q.007.287.118.493a.8.8 0 0 0 .308.313 1 1 0 0 0 .47.106q.283 0 .479-.12a.8.8 0 0 0 .301-.363q.104-.238.103-.586zM10.443 30v-7.273h2.869q.828 0 1.41.316.582.314.888.87.309.555.309 1.279t-.313 1.278-.905.863-1.428.309h-1.829V26.41h1.58q.444 0 .732-.153.29-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153H11.98V30zm11.297-4.922a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.544-.074q-.557 0-.98.277-.42.277-.653.806-.235.526-.235 1.285 0 .76.231 1.293t.654.813q.422.277.997.277.523 0 .892-.184.373-.188.568-.53.2-.34.199-.805l.312.046h-1.875v-1.158h3.044v.916q0 .96-.405 1.648-.405.686-1.115 1.058-.71.37-1.627.37-1.022 0-1.796-.452a3.13 3.13 0 0 1-1.208-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.26-.696.724-1.179.465-.483 1.084-.735a3.5 3.5 0 0 1 1.338-.252q.618 0 1.151.18.532.179.945.505.415.327.678.778.263.447.337.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ js: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "19", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.916 22.727h1.52v5.071q0 .703-.316 1.222a2.1 2.1 0 0 1-.87.799q-.558.28-1.296.28-.657 0-1.193-.23a1.9 1.9 0 0 1-.846-.71q-.311-.48-.309-1.204h1.531q.007.287.117.493a.8.8 0 0 0 .31.313 1 1 0 0 0 .468.106q.285 0 .48-.12a.8.8 0 0 0 .301-.363q.103-.238.103-.586zm6.737 2.092a.9.9 0 0 0-.366-.668q-.324-.238-.877-.238-.377 0-.636.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.85.85 0 0 0 .252.253q.16.103.37.18.21.075.447.129l.653.156q.477.106.874.284.398.177.689.437.291.259.45.61.164.353.168.807-.004.667-.341 1.157-.335.487-.966.757-.63.266-1.516.266-.881 0-1.535-.27a2.25 2.25 0 0 1-1.015-.799q-.362-.533-.38-1.317h1.488q.024.366.21.61.188.242.5.366.316.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.146-.412a1.1 1.1 0 0 0-.419-.284 4 4 0 0 0-.67-.213l-.793-.199q-.92-.224-1.452-.7-.533-.475-.53-1.282-.003-.66.352-1.154.36-.493.984-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.944.77.338.494.348 1.144z",
+ },
+ [],
+ ],
+ ],
+ ],
+ json: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "34", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M7.828 22.727h1.52v5.071q0 .703-.316 1.222a2.1 2.1 0 0 1-.87.799q-.558.28-1.296.28-.657 0-1.193-.23a1.9 1.9 0 0 1-.846-.71q-.311-.48-.308-1.204h1.53q.007.287.117.493a.8.8 0 0 0 .31.313 1 1 0 0 0 .468.106q.285 0 .48-.12a.8.8 0 0 0 .301-.363q.103-.238.103-.586zm6.737 2.092a.9.9 0 0 0-.366-.668q-.324-.238-.877-.238-.377 0-.636.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.85.85 0 0 0 .252.253q.16.103.37.18.21.075.447.129l.653.156q.477.106.874.284.398.177.689.437.29.259.451.61.164.353.167.807-.004.667-.341 1.157-.335.487-.966.757-.628.266-1.516.266-.881 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.363-.533-.38-1.317h1.488q.025.366.21.61.188.242.5.366.316.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.146-.412a1.1 1.1 0 0 0-.419-.284 4 4 0 0 0-.67-.213l-.793-.199q-.92-.224-1.452-.7-.533-.475-.53-1.282-.003-.66.352-1.154.36-.493.984-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.944.77.338.494.348 1.144zm9.29 1.545q0 1.189-.45 2.024a3.13 3.13 0 0 1-1.222 1.275 3.45 3.45 0 0 1-1.733.436 3.44 3.44 0 0 1-1.74-.44 3.14 3.14 0 0 1-1.218-1.275q-.448-.834-.448-2.02 0-1.19.448-2.024a3.1 3.1 0 0 1 1.218-1.272 3.44 3.44 0 0 1 1.74-.44q.963 0 1.733.44.774.437 1.221 1.271.451.835.451 2.025m-1.559 0q0-.77-.23-1.3-.228-.529-.643-.803a1.73 1.73 0 0 0-.973-.273 1.73 1.73 0 0 0-.973.273q-.416.274-.646.803-.228.53-.228 1.3t.227 1.3q.231.529.647.802.415.273.973.273t.973-.273.643-.803q.23-.528.23-1.3m8.782-3.637V30h-1.329l-3.164-4.577h-.053V30h-1.538v-7.273h1.35l3.139 4.574h.064v-4.574z",
+ },
+ [],
+ ],
+ ],
+ ],
+ mkv: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "30", height: "16", x: "1", y: "18", fill: "#155EEF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.87 22.727h1.896l2.003 4.887h.085l2.003-4.887h1.896V30H11.26v-4.734h-.06l-1.88 4.699H8.303l-1.882-4.716h-.06V30H4.869zM14.02 30v-7.273h1.537v3.207h.096l2.617-3.207h1.843l-2.698 3.257L20.145 30h-1.839l-1.992-2.99-.757.923V30zm8.208-7.273 1.758 5.526h.067l1.762-5.526h1.704L25.012 30h-1.981l-2.51-7.273z",
+ },
+ [],
+ ],
+ ],
+ ],
+ mp3: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "29", height: "16", x: "1", y: "18", fill: "#DD2590", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M5.024 22.727H6.92l2.003 4.887h.085l2.003-4.887h1.896V30h-1.491v-4.734h-.06l-1.883 4.699H8.458l-1.882-4.716h-.06V30H5.023zM14.174 30v-7.273h2.87q.827 0 1.41.316.58.314.887.87.309.555.309 1.279t-.313 1.278-.905.863q-.59.309-1.428.309h-1.829V26.41h1.58q.445 0 .732-.153.291-.156.434-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.434-.423q-.291-.153-.738-.153h-1.037V30zm9.12.1q-.795 0-1.417-.274a2.4 2.4 0 0 1-.976-.76 1.9 1.9 0 0 1-.366-1.122h1.548q.015.266.174.469.164.198.434.309t.607.11q.351 0 .621-.125.27-.123.423-.344a.87.87 0 0 0 .153-.508.85.85 0 0 0-.164-.515 1.04 1.04 0 0 0-.461-.355 1.8 1.8 0 0 0-.71-.128h-.679v-1.129h.678q.348 0 .615-.12a1 1 0 0 0 .419-.335q.15-.216.149-.504a.87.87 0 0 0-.494-.806 1.2 1.2 0 0 0-.54-.117 1.4 1.4 0 0 0-.571.113 1 1 0 0 0-.416.317.83.83 0 0 0-.167.483h-1.473q.01-.63.358-1.108.348-.48.938-.75a3.2 3.2 0 0 1 1.339-.273q.752 0 1.317.273.565.274.877.739.316.461.313 1.037.003.61-.38 1.019a1.73 1.73 0 0 1-.991.518v.057q.802.104 1.222.558.422.45.419 1.13.003.62-.36 1.104-.357.483-.99.76-.632.276-1.449.276",
+ },
+ [],
+ ],
+ ],
+ ],
+ mp4: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "29", height: "16", x: "1", y: "18", fill: "#155EEF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.93 22.727h1.897l2.003 4.887h.085l2.003-4.887h1.897V30h-1.492v-4.734h-.06L9.38 29.965H8.365l-1.882-4.716h-.06V30H4.93zM14.082 30v-7.273h2.87q.827 0 1.41.316.582.314.887.87.31.555.31 1.279t-.313 1.278q-.313.555-.906.863-.59.309-1.427.309h-1.83V26.41h1.581q.444 0 .732-.153.29-.156.433-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.433-.423q-.291-.153-.74-.153H15.62V30zm6.322-1.278V27.51l3.037-4.784h1.043v1.676h-.617l-1.915 3.03v.056h4.315v1.233zM23.894 30v-1.648l.028-.536v-5.089h1.442V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ mpeg: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "36", height: "16", x: "1", y: "18", fill: "#155EEF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.954 22.727h1.897l2.003 4.887h.085l2.003-4.887h1.896V30h-1.492v-4.734h-.06l-1.882 4.699H8.388l-1.882-4.716h-.06V30H4.954zM14.104 30v-7.273h2.87q.828 0 1.41.316.582.314.888.87.309.555.309 1.279t-.313 1.278-.905.863q-.59.309-1.428.309h-1.829V26.41h1.58q.444 0 .732-.153.29-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153h-1.037V30zm6.475 0v-7.273h4.9v1.268h-3.362v1.733h3.11v1.268h-3.11v1.736h3.377V30zm10.946-4.922a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.544-.074q-.557 0-.98.277-.419.277-.653.806-.234.526-.234 1.285 0 .76.23 1.293.231.533.654.813.422.277.998.277.522 0 .891-.184.372-.188.568-.53.2-.34.199-.805l.313.046h-1.875v-1.158h3.043v.916q0 .96-.405 1.648a2.76 2.76 0 0 1-1.115 1.058q-.711.37-1.626.37-1.023 0-1.797-.452a3.13 3.13 0 0 1-1.208-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.26-.696.725-1.179a3.1 3.1 0 0 1 1.083-.735 3.5 3.5 0 0 1 1.339-.252q.618 0 1.15.18.532.179.945.505.416.327.678.778.263.447.337.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ pdf: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#D92D20", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.832 30v-7.273h2.87q.826 0 1.41.316.582.314.887.87.31.555.31 1.279t-.313 1.278q-.313.555-.906.863-.59.309-1.427.309h-1.83V26.41h1.581q.444 0 .732-.153.29-.156.433-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.433-.423q-.291-.153-.74-.153H6.37V30zm9.053 0h-2.578v-7.273h2.6q1.095 0 1.889.437.791.433 1.218 1.246.43.814.43 1.947 0 1.136-.43 1.953a2.95 2.95 0 0 1-1.226 1.253q-.795.437-1.903.437m-1.04-1.317h.976q.682 0 1.147-.242.47-.244.703-.756.238-.516.238-1.328 0-.807-.238-1.318a1.54 1.54 0 0 0-.7-.753q-.465-.24-1.146-.241h-.98zM18.582 30v-7.273h4.816v1.268H20.12v1.733h2.958v1.268H20.12V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ "pdf-simple": [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M4.75 4A3.25 3.25 0 0 1 8 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 32 39.25H8A3.25 3.25 0 0 1 4.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M24 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ [
+ "path",
+ {
+ fill: "#D92D20",
+ d: "M25.709 25.344c-1.123 0-2.528.196-2.988.265-1.903-1.987-2.444-3.117-2.566-3.418.165-.424.74-2.034.821-4.103.04-1.036-.178-1.81-.65-2.3a1.7 1.7 0 0 0-1.205-.528c-.573 0-1.535.29-1.535 2.231 0 1.685.785 3.472 1.002 3.934-1.144 3.33-2.372 5.61-2.632 6.08C11.359 29.234 11 30.907 11 31.381c0 .852.607 1.361 1.623 1.361 2.47 0 4.724-4.146 5.097-4.866 1.754-.7 4.102-1.132 4.698-1.235 1.712 1.63 3.692 2.066 4.515 2.066.618 0 2.067 0 2.067-1.49 0-1.383-1.773-1.874-3.291-1.874m-.12.978c1.335 0 1.687.441 1.687.674 0 .147-.055.624-.77.624-.641 0-1.748-.37-2.838-1.174.454-.06 1.127-.124 1.922-.124m-6.538-10.114c.121 0 .202.039.268.13.383.533.074 2.272-.303 3.634-.363-1.167-.636-2.959-.252-3.589.075-.123.16-.175.287-.175m-.648 10.42c.483-.976 1.024-2.398 1.319-3.202.59.987 1.384 1.904 1.843 2.402-1.43.3-2.51.602-3.162.8m-6.444 4.884c-.031-.037-.036-.117-.012-.212.05-.2.434-1.193 3.214-2.436-.398.627-1.02 1.523-1.704 2.192-.48.45-.856.678-1.113.678-.092 0-.22-.025-.385-.222",
+ },
+ [],
+ ],
+ ],
+ ],
+ png: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "28", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.914 30v-7.273h2.87q.826 0 1.41.316.582.314.887.87.31.555.31 1.279t-.313 1.278q-.313.555-.906.863-.59.309-1.427.309h-1.83V26.41h1.581q.444 0 .732-.153.29-.156.433-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.433-.423q-.291-.153-.74-.153H6.453V30zm12.558-7.273V30h-1.328l-3.164-4.577h-.053V30h-1.538v-7.273h1.35l3.139 4.574h.063v-4.574zm6.093 2.351a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.544-.074q-.557 0-.98.277-.419.277-.653.806-.234.526-.234 1.285 0 .76.23 1.293.231.533.654.813.422.277.998.277.522 0 .891-.184.373-.188.568-.53.2-.34.199-.805l.313.046H22.15v-1.158h3.044v.916q0 .96-.405 1.648-.405.686-1.115 1.058-.711.37-1.626.37-1.023 0-1.797-.452a3.13 3.13 0 0 1-1.208-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.26-.696.725-1.179t1.083-.735a3.5 3.5 0 0 1 1.339-.252q.618 0 1.15.18.533.179.945.505.415.327.678.778.263.447.337.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ ppt: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#E62E05", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.818 30v-7.273h2.869q.828 0 1.41.316.582.314.888.87.309.555.309 1.279T9.98 26.47t-.906.863q-.589.309-1.427.309H5.819V26.41H7.4q.444 0 .732-.153.29-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153H6.355V30zm6.474 0v-7.273h2.87q.826 0 1.41.316.582.314.887.87.31.555.31 1.279t-.313 1.278q-.313.555-.906.863-.59.309-1.428.309h-1.828V26.41h1.58q.444 0 .731-.153.292-.156.434-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.434-.423q-.291-.153-.738-.153H12.83V30zm6.198-6.005v-1.268h5.973v1.268h-2.227V30h-1.52v-6.005z",
+ },
+ [],
+ ],
+ ],
+ ],
+ pptx: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "33", height: "16", x: "1", y: "18", fill: "#E62E05", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.743 30v-7.273h2.87q.826 0 1.41.316.582.314.887.87.31.555.31 1.279t-.313 1.278q-.313.555-.906.863-.59.309-1.427.309h-1.83V26.41h1.581q.444 0 .732-.153.29-.156.433-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153H6.281V30zm6.475 0v-7.273h2.87q.826 0 1.41.316.58.314.887.87.309.555.309 1.279t-.313 1.278-.905.863q-.59.309-1.428.309H12.22V26.41h1.58q.444 0 .731-.153.292-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.291-.153-.738-.153h-1.037V30zm6.198-6.005v-1.268h5.973v1.268h-2.227V30h-1.52v-6.005zm8.398-1.268 1.467 2.479h.056l1.474-2.479h1.737l-2.22 3.637L30.598 30h-1.769l-1.492-2.482h-.056L25.789 30h-1.761l2.276-3.636-2.233-3.637z",
+ },
+ [],
+ ],
+ ],
+ ],
+ psd: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "27", height: "16", x: "1", y: "18", fill: "#155EEF", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.98 30v-7.273h2.87q.828 0 1.41.316.582.314.888.87.309.555.309 1.279t-.313 1.278-.905.863q-.59.309-1.428.309H5.982V26.41h1.58q.444 0 .732-.153.29-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153H6.518V30zm10.42-5.181a.9.9 0 0 0-.365-.668q-.324-.238-.877-.238-.377 0-.636.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.9.9 0 0 0 .252.253q.16.103.37.18.21.075.447.129l.653.156q.477.106.874.284.398.177.689.437.291.259.45.61.164.353.168.807-.004.667-.341 1.157-.334.487-.966.757-.629.266-1.516.266-.882 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.363-.533-.38-1.317h1.488q.025.366.21.61.188.242.5.366.316.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.146-.412a1.1 1.1 0 0 0-.419-.284 4 4 0 0 0-.67-.213l-.793-.199q-.92-.224-1.452-.7-.533-.475-.53-1.282-.003-.66.352-1.154.36-.493.984-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.944.77.338.494.348 1.144zM20.587 30h-2.578v-7.273h2.6q1.096 0 1.889.437.792.433 1.218 1.246.43.814.43 1.947 0 1.136-.43 1.953a2.95 2.95 0 0 1-1.225 1.253q-.795.437-1.904.437m-1.04-1.317h.976q.682 0 1.147-.242.47-.244.703-.756.238-.516.238-1.328 0-.807-.238-1.318a1.54 1.54 0 0 0-.7-.753q-.465-.24-1.146-.241h-.98z",
+ },
+ [],
+ ],
+ ],
+ ],
+ rar: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "27", height: "16", x: "1", y: "18", fill: "#344054", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.83 30v-7.273h2.869q.824 0 1.406.295.586.291.891.827.31.533.31 1.254 0 .724-.313 1.246-.313.519-.906.796-.59.277-1.427.277H5.738v-1.236h1.673q.44 0 .732-.12a.87.87 0 0 0 .433-.363q.145-.24.145-.6a1.2 1.2 0 0 0-.145-.61.9.9 0 0 0-.437-.377q-.292-.132-.735-.132H6.367V30zm3.927-3.31L10.565 30H8.867l-1.768-3.31zM12.645 30h-1.647l2.51-7.273h1.982L17.997 30h-1.648l-1.822-5.61h-.056zm-.103-2.859h3.893v1.2h-3.893zM18.872 30v-7.273h2.87q.824 0 1.406.295.586.291.891.827.31.533.31 1.254 0 .724-.313 1.246-.313.519-.906.796-.59.277-1.427.277H19.78v-1.236h1.673q.44 0 .732-.12a.87.87 0 0 0 .433-.363q.145-.24.145-.6a1.2 1.2 0 0 0-.145-.61.9.9 0 0 0-.437-.377q-.292-.132-.735-.132H20.41V30zm3.928-3.31L24.607 30H22.91l-1.768-3.31z",
+ },
+ [],
+ ],
+ ],
+ ],
+ rss: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.798 30v-7.273h2.87q.823 0 1.406.295.585.291.891.827.309.533.309 1.254 0 .724-.313 1.246-.312.519-.905.796-.59.277-1.428.277h-1.92v-1.236H7.38q.44 0 .731-.12a.87.87 0 0 0 .434-.363q.145-.24.145-.6a1.2 1.2 0 0 0-.145-.61.9.9 0 0 0-.437-.377q-.292-.132-.735-.132H6.336V30zm3.928-3.31L10.533 30H8.836l-1.769-3.31zm6.58-1.871a.9.9 0 0 0-.366-.668q-.323-.238-.877-.238-.376 0-.636.107a.9.9 0 0 0-.397.288.7.7 0 0 0-.135.419.6.6 0 0 0 .081.34.85.85 0 0 0 .252.253q.16.103.37.18.21.075.447.129l.654.156q.476.106.873.284.397.177.69.437.29.259.45.61.165.353.167.807-.003.667-.34 1.157-.334.487-.967.757-.628.266-1.516.266-.881 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.362-.533-.38-1.317h1.488q.025.366.21.61.188.242.5.366.316.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.145-.412a1.1 1.1 0 0 0-.42-.284 4 4 0 0 0-.67-.213l-.793-.199q-.92-.224-1.452-.7-.532-.475-.529-1.282-.003-.66.352-1.154.359-.493.983-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.945.77t.348 1.144zm6.553 0a.9.9 0 0 0-.366-.668q-.323-.238-.877-.238-.377 0-.636.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.9.9 0 0 0 .252.253q.16.103.37.18.21.075.447.129l.653.156q.477.106.874.284.398.177.689.437.291.259.45.61.165.353.168.807-.003.667-.341 1.157-.335.487-.966.757-.628.266-1.516.266-.882 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.362-.533-.38-1.317h1.488q.024.366.21.61.188.242.5.366.316.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .451-.316.73.73 0 0 0 .16-.465q0-.244-.146-.412a1.1 1.1 0 0 0-.419-.284 4 4 0 0 0-.67-.213l-.793-.199q-.92-.224-1.452-.7-.533-.475-.53-1.282-.003-.66.352-1.154.36-.493.984-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.944.77.338.494.348 1.144z",
+ },
+ [],
+ ],
+ ],
+ ],
+ sql: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "27", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M9.053 24.819a.9.9 0 0 0-.366-.668q-.323-.238-.877-.238-.376 0-.636.107a.9.9 0 0 0-.397.288.7.7 0 0 0-.135.419.6.6 0 0 0 .081.34.85.85 0 0 0 .253.253q.16.103.369.18.21.075.447.129l.654.156q.476.106.873.284.397.177.69.437.29.259.45.61.165.353.167.807-.004.667-.34 1.157-.334.487-.967.757-.628.266-1.516.266-.88 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.362-.533-.38-1.317h1.488q.026.366.21.61.188.242.5.366.317.12.714.12.39 0 .679-.113a1.04 1.04 0 0 0 .45-.316.73.73 0 0 0 .16-.465q0-.244-.145-.412a1.1 1.1 0 0 0-.42-.284 4 4 0 0 0-.67-.213l-.792-.199q-.92-.224-1.453-.7-.532-.475-.529-1.282-.003-.66.352-1.154.359-.493.983-.77.625-.277 1.42-.277.81 0 1.414.277.607.276.945.77t.348 1.144zm5.352 2.653h1.307l.657.845.646.753 1.219 1.527h-1.435l-.838-1.03-.43-.611zm3.939-1.108q0 1.189-.451 2.024a3.13 3.13 0 0 1-1.222 1.275 3.45 3.45 0 0 1-1.733.436 3.44 3.44 0 0 1-1.74-.44 3.13 3.13 0 0 1-1.218-1.275q-.447-.834-.447-2.02 0-1.19.447-2.024a3.1 3.1 0 0 1 1.218-1.272 3.44 3.44 0 0 1 1.74-.44q.963 0 1.733.44.774.437 1.222 1.271.45.835.45 2.025m-1.56 0q0-.77-.23-1.3-.228-.529-.643-.803a1.73 1.73 0 0 0-.973-.273 1.73 1.73 0 0 0-.973.273q-.415.274-.646.803-.228.53-.228 1.3t.228 1.3q.231.529.646.802t.973.273.973-.273.643-.803q.23-.528.23-1.3M19.484 30v-7.273h1.537v6.005h3.118V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ svg: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "28", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M8.962 24.819a.9.9 0 0 0-.366-.668q-.322-.238-.877-.238-.376 0-.635.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.85.85 0 0 0 .252.253q.16.103.37.18.209.075.447.129l.653.156q.475.106.874.284.397.177.688.437.292.259.451.61.164.353.167.807-.003.667-.34 1.157-.335.487-.966.757-.63.266-1.517.266-.88 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.362-.533-.38-1.317H6.27q.026.366.21.61.188.242.5.366.317.12.714.12.39 0 .679-.113a1.04 1.04 0 0 0 .45-.316.73.73 0 0 0 .16-.465q0-.244-.145-.412a1.1 1.1 0 0 0-.42-.284 4 4 0 0 0-.67-.213l-.792-.199q-.92-.224-1.453-.7-.532-.475-.529-1.282-.003-.66.352-1.154.359-.493.983-.77.625-.277 1.42-.277.81 0 1.414.277.607.276.945.77t.348 1.144zm3.922-2.092 1.757 5.526h.068l1.761-5.526h1.705L15.668 30h-1.982l-2.51-7.273zm10.578 2.351a1.6 1.6 0 0 0-.21-.458q-.135-.203-.33-.34a1.4 1.4 0 0 0-.44-.218 1.9 1.9 0 0 0-.543-.074q-.558 0-.98.277-.42.277-.654.806-.234.526-.234 1.285 0 .76.23 1.293.233.533.654.813.423.277.998.277.522 0 .892-.184.372-.188.568-.53.198-.34.198-.805l.313.046h-1.875v-1.158h3.043v.916q0 .96-.404 1.648a2.76 2.76 0 0 1-1.116 1.058q-.71.37-1.626.37-1.022 0-1.797-.452a3.13 3.13 0 0 1-1.207-1.289q-.43-.837-.43-1.988 0-.884.256-1.577.26-.696.724-1.179a3.1 3.1 0 0 1 1.083-.735 3.5 3.5 0 0 1 1.34-.252q.616 0 1.15.18.533.179.944.505.415.327.678.778.264.447.338.987z",
+ },
+ [],
+ ],
+ ],
+ ],
+ tiff: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "28", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.764 23.995v-1.268h5.973v1.268H8.511V30H6.99v-6.005zm8.495-1.268V30H11.72v-7.273zM14.524 30v-7.273h4.815v1.268h-3.278v1.733h2.958v1.268h-2.958V30zm5.85 0v-7.273h4.815v1.268H21.91v1.733h2.958v1.268H21.91V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ txt: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "27", height: "16", x: "1", y: "18", fill: "#475467", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.601 23.995v-1.268h5.973v1.268H8.348V30h-1.52v-6.005zM13 22.727l1.466 2.479h.057l1.474-2.479h1.736l-2.22 3.637L17.784 30h-1.768l-1.492-2.482h-.057L12.975 30h-1.762l2.277-3.636-2.234-3.637zm5.43 1.268v-1.268h5.972v1.268h-2.226V30h-1.52v-6.005z",
+ },
+ [],
+ ],
+ ],
+ ],
+ wav: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "30", height: "16", x: "1", y: "18", fill: "#DD2590", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "m6.428 30-2.08-7.273h1.68l1.203 5.054h.06l1.328-5.054h1.439l1.324 5.064h.064l1.204-5.064h1.68L12.249 30H10.75l-1.385-4.755h-.057L7.927 30zm9.246 0h-1.648l2.51-7.273h1.982L21.025 30h-1.647l-1.822-5.61h-.057zm-.103-2.859h3.892v1.2H15.57zm6.735-4.414 1.758 5.526h.068l1.761-5.526h1.705L25.09 30h-1.98l-2.51-7.273z",
+ },
+ [],
+ ],
+ ],
+ ],
+ webp: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "36", height: "16", x: "1", y: "18", fill: "#7F56D9", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "m6.484 30-2.08-7.273h1.679l1.204 5.054h.06l1.328-5.054h1.438l1.325 5.064h.064l1.204-5.064h1.68L12.303 30h-1.498L9.42 25.245h-.057L7.983 30zm8.724 0v-7.273h4.9v1.268h-3.362v1.733h3.11v1.268h-3.11v1.736h3.377V30zm6.123 0v-7.273h2.912q.803 0 1.339.238t.806.66q.27.42.27.967 0 .426-.17.749-.17.32-.47.525-.294.203-.674.288v.071q.415.018.778.234.365.217.593.608.227.387.227.923 0 .579-.288 1.033-.284.452-.841.714-.558.263-1.374.263zm1.538-1.257h1.253q.643 0 .938-.245a.82.82 0 0 0 .295-.66 1 1 0 0 0-.146-.533 1 1 0 0 0-.415-.363 1.4 1.4 0 0 0-.636-.13h-1.289zm0-2.972h1.14q.316 0 .561-.11a.93.93 0 0 0 .39-.32.83.83 0 0 0 .146-.494.8.8 0 0 0-.28-.635q-.277-.242-.789-.242H22.87zM27.943 30v-7.273h2.869q.827 0 1.41.316.582.314.888.87.309.555.309 1.279t-.313 1.278-.906.863q-.589.309-1.427.309h-1.829V26.41h1.58q.444 0 .732-.153.29-.156.433-.43.146-.276.146-.635 0-.363-.146-.632a.97.97 0 0 0-.433-.423q-.29-.153-.739-.153H29.48V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ xls: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "26", height: "16", x: "1", y: "18", fill: "#079455", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "m6.386 22.727 1.467 2.479h.057l1.474-2.479h1.736L8.9 26.364 11.17 30H9.4l-1.49-2.482h-.057L6.362 30H4.6l2.276-3.636-2.233-3.637zM12.093 30v-7.273h1.538v6.005h3.118V30zm9.62-5.181a.9.9 0 0 0-.367-.668q-.322-.238-.877-.238-.376 0-.635.107a.9.9 0 0 0-.398.288.7.7 0 0 0-.135.419.6.6 0 0 0 .082.34.85.85 0 0 0 .252.253q.16.103.37.18.209.075.447.129l.653.156q.475.106.874.284.397.177.688.437.292.259.451.61.165.353.167.807-.003.667-.34 1.157-.334.487-.966.757-.63.266-1.517.266-.88 0-1.534-.27a2.25 2.25 0 0 1-1.015-.799q-.363-.533-.38-1.317h1.487q.025.366.21.61.188.242.5.366.317.12.714.12.39 0 .679-.113a1.04 1.04 0 0 0 .45-.316.73.73 0 0 0 .16-.465q0-.244-.145-.412a1.1 1.1 0 0 0-.42-.284 4 4 0 0 0-.67-.213l-.792-.199q-.92-.224-1.453-.7-.532-.475-.529-1.282-.003-.66.352-1.154.359-.493.983-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.945.77t.348 1.144z",
+ },
+ [],
+ ],
+ ],
+ ],
+ xlsx: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "33", height: "16", x: "1", y: "18", fill: "#079455", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "m6.312 22.727 1.467 2.479h.057l1.473-2.479h1.737l-2.22 3.637L11.096 30H9.327l-1.491-2.482h-.057L6.287 30H4.526l2.276-3.636-2.233-3.637zM12.02 30v-7.273h1.537v6.005h3.118V30zm9.619-5.181a.9.9 0 0 0-.366-.668q-.323-.238-.877-.238-.376 0-.636.107a.9.9 0 0 0-.397.288.7.7 0 0 0-.135.419.6.6 0 0 0 .081.34.9.9 0 0 0 .253.253q.16.103.369.18.21.075.447.129l.654.156q.476.106.873.284.398.177.69.437.29.259.45.61.164.353.167.807-.004.667-.34 1.157-.334.487-.967.757-.628.266-1.516.266-.881 0-1.534-.27a2.25 2.25 0 0 1-1.016-.799q-.362-.533-.38-1.317h1.488q.025.366.21.61.188.242.5.366.317.12.714.12.39 0 .678-.113a1.04 1.04 0 0 0 .452-.316.73.73 0 0 0 .16-.465q-.001-.244-.146-.412a1.1 1.1 0 0 0-.42-.284 4 4 0 0 0-.67-.213l-.792-.199q-.92-.224-1.453-.7-.532-.475-.529-1.282-.003-.66.352-1.154.359-.493.983-.77.625-.277 1.42-.277.81 0 1.414.277.608.276.945.77t.348 1.144zm4.05-2.092 1.466 2.479h.057l1.473-2.479h1.737l-2.22 3.637L30.471 30h-1.769l-1.491-2.482h-.057L25.662 30h-1.761l2.276-3.636-2.233-3.637z",
+ },
+ [],
+ ],
+ ],
+ ],
+ xml: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "28", height: "16", x: "1", y: "18", fill: "#444CE7", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "m6.088 22.727 1.466 2.479h.057l1.474-2.479h1.736l-2.22 3.637L10.872 30h-1.77l-1.491-2.482h-.057L6.063 30H4.3l2.277-3.636-2.234-3.637zm5.706 0h1.896l2.003 4.887h.086l2.002-4.887h1.897V30h-1.492v-4.734h-.06l-1.882 4.699h-1.016l-1.882-4.716h-.06V30h-1.492zM20.944 30v-7.273h1.538v6.005H25.6V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+ zip: [
+ "svg",
+ { width: "40", height: "40", fill: "none", viewBox: "0 0 40 40", "aria-hidden": "true" },
+ [
+ [
+ "path",
+ {
+ stroke: "#D5D7DA",
+ "stroke-width": "1.5",
+ d: "M7.75 4A3.25 3.25 0 0 1 11 .75h16c.121 0 .238.048.323.134l10.793 10.793a.46.46 0 0 1 .134.323v24A3.25 3.25 0 0 1 35 39.25H11A3.25 3.25 0 0 1 7.75 36z",
+ },
+ [],
+ ],
+ ["path", { stroke: "#D5D7DA", "stroke-width": "1.5", d: "M27 .5V8a4 4 0 0 0 4 4h7.5" }, []],
+ ["rect", { width: "22", height: "16", x: "1", y: "18", fill: "#344054", rx: "2" }, []],
+ [
+ "path",
+ {
+ fill: "#fff",
+ d: "M4.58 30v-.913l3.63-5.092H4.573v-1.268h5.568v.913L6.51 28.732h3.64V30zm8.286-7.273V30h-1.538v-7.273zM14.131 30v-7.273h2.87q.826 0 1.41.316.58.314.887.87.309.555.309 1.279t-.312 1.278-.906.863q-.59.309-1.428.309h-1.828V26.41h1.58q.444 0 .731-.153.292-.156.434-.43.145-.276.145-.635 0-.363-.145-.632a.97.97 0 0 0-.434-.423q-.291-.153-.738-.153h-1.037V30z",
+ },
+ [],
+ ],
+ ],
+ ],
+}
+
+// FileIcon resolves a MIME type to its category (`categorizeMimeType`).
+const codeSubtypes = [
+ "json",
+ "javascript",
+ "x-javascript",
+ "typescript",
+ "typescriptreact",
+ "xml",
+ "html",
+ "css",
+ "yaml",
+ "x-yaml",
+ "toml",
+ "x-toml",
+ "markdown",
+ "php",
+ "x-php",
+ "java",
+ "python",
+ "ruby",
+ "x-ruby",
+ "swift",
+ "go",
+ "x-go",
+ "c",
+ "x-c",
+ "cpp",
+ "x-cpp",
+ "csharp",
+ "x-csharp",
+]
+const documentSubtypes = [
+ "pdf",
+ "msword",
+ "vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "vnd.ms-excel",
+ "vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "vnd.ms-powerpoint",
+ "vnd.openxmlformats-officedocument.presentationml.presentation",
+]
+const categorize = (mime: string) => {
+ const [kind, subtype] = mime.split("/")
+ if (!kind || !subtype) return "unknown"
+ if (kind === "image" || kind === "audio" || kind === "video") return kind
+ if (kind === "application" || kind === "text") {
+ if (codeSubtypes.includes(subtype)) return "code"
+ if (documentSubtypes.includes(subtype)) return "document"
+ }
+ return "unknown"
+}
+
+/** FileIcon's type resolution: a known type, a MIME category, else `empty`. */
+const resolveType = (type: string) => {
+ const isMime = type.includes("/")
+ const subtype = (isMime ? type.split("/")[1] : undefined) ?? type
+ if (SUPPORTED_FILE_TYPES.includes(subtype)) return subtype
+ const category = isMime ? categorize(type) : undefined
+ return category !== undefined && SUPPORTED_FILE_TYPES.includes(category) ? category : "empty"
+}
+
+const renderTree = (
+ h: HtmlBuilder,
+ [tag, attributes, children]: Tree,
+ className?: string,
+): Html =>
+ h[tag](
+ [
+ ...Object.entries(attributes).map(([key, value]) => h.Attribute(key, value)),
+ ...(className ? [h.Class(className)] : []),
+ ],
+ children.map((child) => (typeof child === "string" ? child : renderTree(h, child))),
+ )
+
+/** Port of ``. */
+export const fileIcon = (
+ h: HtmlBuilder,
+ type: string,
+ options: { readonly className?: string } = {},
+): Html => renderTree(h, icons[resolveType(type)] ?? icons.empty!, options.className)
diff --git a/apps/web-foldkit/src/chat/image-viewer.ts b/apps/web-foldkit/src/chat/image-viewer.ts
new file mode 100644
index 000000000..db03b0426
--- /dev/null
+++ b/apps/web-foldkit/src/chat/image-viewer.ts
@@ -0,0 +1,335 @@
+import EmblaCarousel from "embla-carousel"
+import { Effect, Queue, Schema, Stream } from "effect"
+import { Mount } from "foldkit"
+import type { Html, HtmlBuilder } from "foldkit/html"
+import { defineMessageUnion } from "foldkit/message"
+import {
+ IconChevronLeft,
+ IconChevronRight,
+ IconClose,
+ IconCopy,
+ IconDownload,
+ IconExternalLink,
+} from "../icons"
+import { avatar } from "../ui/avatar"
+import { ariaButton, button } from "../ui/button"
+import { portalOverlay } from "../ui/aria/overlay"
+import type { ChatAttachmentView } from "./attachments"
+
+/**
+ * Port of `ImageViewerModal` (`components/chat/image-viewer-modal.tsx`) in its open state: a plain
+ * `createPortal` to the end of ``, no dialog role, no focus trap and no scroll lock.
+ */
+
+export type ImageViewerAction = "download" | "copyUrl" | "openInBrowser"
+
+export interface ImageViewerInputs {
+ readonly images: ReadonlyArray
+ readonly index: number
+ readonly author: {
+ readonly name: string
+ readonly avatarUrl: string | null
+ readonly seed?: string
+ } | null
+ readonly createdAtMs: number
+ readonly toClose: () => M
+ readonly toSelect: (index: number) => M
+ /** Toolbar side effects (legacy downloads, copies the URL with a toast, or opens a tab). */
+ readonly toAction?: (action: ImageViewerAction, image: ChatAttachmentView) => M
+}
+
+const Intent = defineMessageUnion({ PressedClose: {}, PressedSelect: { index: Schema.Number } })
+type Intent = typeof Intent.Type
+
+// The selected slide as the Model has it, which the carousels scroll to (embla owns their transforms).
+const SELECTED_ATTRIBUTE = "data-selected-index"
+
+const readSelected = (element: Element) => Number(element.getAttribute(SELECTED_ATTRIBUTE) ?? 0)
+
+const NO_RELEASE = () => {}
+
+/** Runs `follow(index)` whenever the view changes the selected index on `element`. */
+const observeSelected = (element: Element, follow: (index: number) => void) => {
+ const observer = new MutationObserver(() => follow(readSelected(element)))
+ observer.observe(element, { attributes: true, attributeFilter: [SELECTED_ATTRIBUTE] })
+ return observer
+}
+
+/**
+ * The main carousel: vanilla embla on the viewport (`useEmblaCarousel({ startIndex, loop: false })`).
+ * A swipe or drag selects a slide; a selection from the Model (arrows, thumbs, keys) scrolls to it.
+ */
+const MainCarousel = Mount.defineStream("ImageViewerMainCarousel", {
+ messages: [Intent.PressedClose, Intent.PressedSelect],
+ execute: ({ element }) =>
+ Stream.callback((queue) =>
+ Effect.acquireRelease(
+ Effect.sync(() => {
+ if (!(element instanceof HTMLElement)) return NO_RELEASE
+ const embla = EmblaCarousel(element, { startIndex: readSelected(element), loop: false })
+ embla.on("select", () => {
+ const index = embla.selectedScrollSnap()
+ if (index !== readSelected(element)) Queue.offerUnsafe(queue, Intent.PressedSelect({ index }))
+ })
+ const observer = observeSelected(element, (index) => {
+ if (index !== embla.selectedScrollSnap()) embla.scrollTo(index)
+ })
+ // `imageViewer.prev` / `imageViewer.next`: embla moves at once, so fast repeats all count.
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "ArrowLeft") embla.scrollPrev()
+ if (event.key === "ArrowRight") embla.scrollNext()
+ }
+ document.addEventListener("keydown", onKeyDown)
+ return () => {
+ document.removeEventListener("keydown", onKeyDown)
+ observer.disconnect()
+ embla.destroy()
+ }
+ }),
+ (release) => Effect.sync(release),
+ ).pipe(Effect.flatMap(() => Effect.never)),
+ ),
+})
+
+/** The thumbnail strip (`containScroll: "keepSnaps"`, `dragFree`), kept on the selected slide. */
+const ThumbCarousel = Mount.defineStream("ImageViewerThumbCarousel", {
+ messages: [Intent.PressedClose, Intent.PressedSelect],
+ execute: ({ element }) =>
+ Stream.callback(() =>
+ Effect.acquireRelease(
+ Effect.sync(() => {
+ if (!(element instanceof HTMLElement)) return NO_RELEASE
+ const embla = EmblaCarousel(element, { containScroll: "keepSnaps", dragFree: true })
+ embla.scrollTo(readSelected(element))
+ const observer = observeSelected(element, (index) => embla.scrollTo(index))
+ return () => {
+ observer.disconnect()
+ embla.destroy()
+ }
+ }),
+ (release) => Effect.sync(release),
+ ).pipe(Effect.flatMap(() => Effect.never)),
+ ),
+})
+
+/** Portal + the Escape hotkey + backdrop click (`target === currentTarget`); the carousel owns the arrows. */
+const ImageViewerPortal = Mount.defineStream("ImageViewerPortal", {
+ messages: [Intent.PressedClose, Intent.PressedSelect],
+ execute: ({ element }) =>
+ Stream.callback((queue) =>
+ Effect.acquireRelease(
+ Effect.sync(() => {
+ const releasePortal = portalOverlay(element, { isModal: false })
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") Queue.offerUnsafe(queue, Intent.PressedClose())
+ }
+ const onClick = (event: Event) => {
+ if (event.target === event.currentTarget)
+ Queue.offerUnsafe(queue, Intent.PressedClose())
+ }
+ document.addEventListener("keydown", onKeyDown)
+ element.addEventListener("click", onClick)
+ return () => {
+ document.removeEventListener("keydown", onKeyDown)
+ element.removeEventListener("click", onClick)
+ releasePortal()
+ }
+ }),
+ (release) => Effect.sync(release),
+ ).pipe(Effect.flatMap(() => Effect.never)),
+ ),
+})
+
+const toolbarButton = (h: HtmlBuilder, onPress: M | undefined, icon: Html): Html =>
+ // `TooltipTrigger` is a bare React Aria Button wrapping the action Button; the tooltip shows on hover only.
+ ariaButton(h, { className: "react-aria-Button" }, [
+ button(
+ h,
+ {
+ intent: "plain",
+ size: "sq-sm",
+ ...(onPress === undefined ? {} : { onPress }),
+ className: "text-white hover:bg-white/10",
+ },
+ [icon],
+ ),
+ ])
+
+const navButton = (h: HtmlBuilder, side: "left" | "right", onPress: M): Html =>
+ button(
+ h,
+ {
+ intent: "plain",
+ size: "sq-md",
+ isCircle: true,
+ onPress,
+ className: `absolute top-1/2 ${side === "left" ? "left-4" : "right-4"} -translate-y-1/2 bg-black/50 text-white hover:bg-black/70`,
+ attributes: [h.AriaLabel(side === "left" ? "Previous image" : "Next image")],
+ },
+ [
+ side === "left"
+ ? IconChevronLeft(h, { className: "size-6" })
+ : IconChevronRight(h, { className: "size-6" }),
+ ],
+ )
+
+/** Marks an overlay that legacy portals from inside the message list (React tree, not DOM). */
+export const LIST_PORTAL_ATTRIBUTE = "list-portal"
+
+export const imageViewerView = (h: HtmlBuilder, inputs: ImageViewerInputs): Html => {
+ const { images, author } = inputs
+ const selectedIndex = Math.min(Math.max(inputs.index, 0), Math.max(images.length - 1, 0))
+ const current = images[selectedIndex]
+ const hasMany = images.length > 1
+ const act = (action: ImageViewerAction) =>
+ inputs.toAction && current ? inputs.toAction(action, current) : undefined
+ const actions: ReadonlyArray = [
+ [act("download"), IconDownload(h, { className: "size-4" })],
+ [act("copyUrl"), IconCopy(h, { className: "size-4" })],
+ [act("openInBrowser"), IconExternalLink(h, { className: "size-4" })],
+ [inputs.toClose(), IconClose(h, { className: "size-4" })],
+ ]
+ const toMessage = (intent: Intent) =>
+ intent._tag === "PressedClose" ? inputs.toClose() : inputs.toSelect(intent.index)
+ const onMount = Mount.mapMessage(ImageViewerPortal(), toMessage)
+ return h.div(
+ [
+ h.Class(
+ "fixed inset-0 isolate z-9999 flex items-center justify-center bg-black/90 transition-opacity duration-200",
+ ),
+ // Legacy renders the viewer through a React portal inside the message list, so React's
+ // pointerleave never sees the pointer leave the list for it. Hover tracking reads this.
+ h.DataAttribute(LIST_PORTAL_ATTRIBUTE, ""),
+ h.OnMount(onMount),
+ ],
+ [
+ ...(author
+ ? [
+ h.div(
+ [h.Class("absolute top-5 left-5 flex items-center gap-2")],
+ [
+ avatar(h, {
+ src: author.avatarUrl,
+ alt: author.name,
+ ...(author.seed === undefined ? {} : { seed: author.seed }),
+ size: "md",
+ }),
+ h.div(
+ [h.Class("flex flex-col")],
+ [
+ h.span([h.Class("text-sm text-white")], [author.name]),
+ h.span(
+ [h.Class("text-muted-fg text-xs")],
+ [new Date(inputs.createdAtMs).toLocaleString()],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ]
+ : []),
+ h.div(
+ [h.Class("relative mx-36 w-full max-w-[90vw]")],
+ [
+ h.div(
+ [
+ h.Class("overflow-hidden"),
+ h.Attribute(SELECTED_ATTRIBUTE, String(selectedIndex)),
+ h.OnMount(Mount.mapMessage(MainCarousel(), toMessage)),
+ ],
+ [
+ h.div(
+ [h.Class("flex")],
+ images.map((image) =>
+ h.keyed("div")(
+ image.id,
+ [h.Class("flex min-w-0 flex-[0_0_100%] items-center justify-center")],
+ [
+ h.img([
+ h.Attribute("src", image.url),
+ h.Attribute("alt", image.fileName),
+ h.Class("max-h-[70vh] max-w-full rounded-md"),
+ ]),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ...(hasMany
+ ? [
+ navButton(h, "left", inputs.toSelect(Math.max(selectedIndex - 1, 0))),
+ navButton(
+ h,
+ "right",
+ inputs.toSelect(Math.min(selectedIndex + 1, images.length - 1)),
+ ),
+ ]
+ : []),
+ ],
+ ),
+ ...(hasMany
+ ? [
+ h.div(
+ [
+ h.Class(
+ "absolute top-5 left-1/2 -translate-x-1/2 rounded-md bg-black/50 px-3 py-1.5 text-sm text-white",
+ ),
+ ],
+ [String(selectedIndex + 1), " of ", String(images.length)],
+ ),
+ ]
+ : []),
+ h.div(
+ [
+ h.Class(
+ "absolute top-5 right-5 flex gap-1 rounded-md border border-white/10 bg-black/50 p-1",
+ ),
+ ],
+ actions.map(([onPress, icon]) => toolbarButton(h, onPress, icon)),
+ ),
+ ...(hasMany
+ ? [
+ h.div(
+ [h.Class("absolute bottom-5 left-1/2 w-full max-w-2xl -translate-x-1/2 px-8")],
+ [
+ h.div(
+ [
+ h.Class("overflow-hidden rounded-md"),
+ h.Attribute(SELECTED_ATTRIBUTE, String(selectedIndex)),
+ h.OnMount(Mount.mapMessage(ThumbCarousel(), toMessage)),
+ ],
+ [
+ h.div(
+ [h.Class("flex gap-2")],
+ images.map((image, index) =>
+ button(
+ h,
+ {
+ intent: "plain",
+ onPress: inputs.toSelect(index),
+ className: `relative min-w-0 flex-[0_0_80px] overflow-hidden rounded border-2 p-0 transition-all ${
+ index === selectedIndex
+ ? "border-white opacity-100"
+ : "border-transparent opacity-50 hover:opacity-75"
+ }`,
+ },
+ [
+ h.img([
+ h.Attribute("src", image.url),
+ h.Attribute("alt", image.fileName),
+ h.Class("h-16 w-20 object-cover"),
+ ]),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ]
+ : []),
+ ],
+ )
+}
diff --git a/apps/web-foldkit/src/chat/live-state.ts b/apps/web-foldkit/src/chat/live-state.ts
new file mode 100644
index 000000000..2daa5a956
--- /dev/null
+++ b/apps/web-foldkit/src/chat/live-state.ts
@@ -0,0 +1,210 @@
+import { ACTOR_SERVICE_ERROR_UI_MESSAGE, isTemporaryActorServiceError } from "@hazel/domain"
+import type { MessageEmbed } from "@hazel/domain/models"
+import { MessageId } from "@hazel/schema"
+import { Effect, Option, Queue, Schema, Stream } from "effect"
+import { defineMessageUnion } from "foldkit/message"
+import { defineTaggedUnion } from "foldkit/schema"
+
+/**
+ * AI replies streamed by the Rivet `message` actor: `useMessageActor` (`hooks/use-message-actor.ts`)
+ * as a connection Stream plus a reducer over its events. The page keeps one `LiveActorState` per
+ * connected message; cached snapshots (completed or failed) never connect.
+ */
+
+// MODEL
+
+export const LiveStatus = Schema.Literals(["idle", "active", "completed", "failed"])
+
+export const LiveActorState = Schema.Struct({
+ status: LiveStatus,
+ text: Schema.String,
+ isStreaming: Schema.Boolean,
+ progress: Schema.NullOr(Schema.Number),
+ error: Schema.NullOr(Schema.String),
+ /** Agent steps are counted (they end the loading state) but not rendered yet. */
+ stepCount: Schema.Number,
+})
+export type LiveActorState = typeof LiveActorState.Type
+
+export const LiveStates = Schema.Record(Schema.String, LiveActorState)
+export type LiveStates = typeof LiveStates.Type
+
+export const initialLiveState: LiveActorState = {
+ status: "idle",
+ text: "",
+ isStreaming: false,
+ progress: null,
+ error: null,
+ stepCount: 0,
+}
+
+const errorMessageOf = (error: unknown): string => {
+ if (typeof error === "string") return error
+ if (error instanceof Error) return error.message
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string")
+ return error.message
+ return "Connection failed. Please try again."
+}
+
+/** `normalizeMessageActorError`. */
+export const normalizeActorError = (error: unknown): string =>
+ isTemporaryActorServiceError(error) ? ACTOR_SERVICE_ERROR_UI_MESSAGE : errorMessageOf(error)
+
+type EmbedType = MessageEmbed.MessageEmbed
+
+/** The embed that turns live state on, if any (`MessageEmbeds`' `liveStateEmbed`). */
+export const liveEmbedOf = (embeds: ReadonlyArray | null) =>
+ embeds?.find((embed) => embed.liveState?.enabled === true)?.liveState ?? null
+
+/** `stateFromCache`, for snapshots the actor already finished (`shouldUseCached`). */
+export const cachedLiveState = (embeds: ReadonlyArray | null): LiveActorState | null => {
+ const cached = liveEmbedOf(embeds)?.cached
+ if (cached === undefined || (cached.status !== "completed" && cached.status !== "failed")) return null
+ return {
+ status: cached.status,
+ text: cached.text ?? "",
+ isStreaming: false,
+ progress: cached.progress ?? (cached.status === "completed" ? 100 : null),
+ error: cached.error ? normalizeActorError(cached.error) : null,
+ stepCount: cached.steps?.length ?? 0,
+ }
+}
+
+/** Messages that open an actor connection: live, and not answered from the cache. */
+export const connectedMessageIds = (
+ messages: ReadonlyArray<{ readonly id: MessageId; readonly embeds: ReadonlyArray | null }>,
+): ReadonlyArray =>
+ messages
+ .filter((message) => liveEmbedOf(message.embeds) !== null && cachedLiveState(message.embeds) === null)
+ .map((message) => message.id)
+ .sort()
+
+// MESSAGE
+
+/** The actor's events (`MessageActorEvents`), decoded; the snapshot is `getState`'s answer. */
+export const LiveEvent = defineTaggedUnion({
+ Snapshot: { state: LiveActorState },
+ Started: {},
+ Progress: { progress: Schema.Number },
+ TextChunk: { fullText: Schema.String },
+ TextUpdate: { text: Schema.String },
+ StreamEnd: { text: Schema.String },
+ Completed: {},
+ Failed: { error: Schema.String },
+ StepAdded: {},
+})
+export type LiveEvent = typeof LiveEvent.Type
+
+export const Message = defineMessageUnion({
+ ReceivedLiveEvent: { messageId: MessageId, event: LiveEvent },
+})
+export type Message = typeof Message.Type
+
+/** `useMessageActor`'s `setState` updaters. */
+const reduce = (state: LiveActorState, event: LiveEvent): LiveActorState =>
+ LiveEvent.match(event, {
+ Snapshot: ({ state: snapshot }) => snapshot,
+ Started: () => ({ ...state, status: "active" }),
+ Progress: ({ progress }) => ({ ...state, progress }),
+ TextChunk: ({ fullText }) => ({ ...state, text: fullText, isStreaming: true }),
+ TextUpdate: ({ text }) => ({ ...state, text }),
+ StreamEnd: ({ text }) => ({ ...state, text, isStreaming: false }),
+ Completed: () => ({ ...state, status: "completed", isStreaming: false, progress: 100 }),
+ Failed: ({ error }) => ({ ...state, status: "failed", error, isStreaming: false }),
+ StepAdded: () => ({ ...state, stepCount: state.stepCount + 1 }),
+ })
+
+export const applyMessage = (states: LiveStates, message: Message): LiveStates =>
+ Message.match(message, {
+ ReceivedLiveEvent: ({ messageId, event }) => ({
+ ...states,
+ [messageId]: reduce(states[messageId] ?? initialLiveState, event),
+ }),
+ })
+
+// STREAM
+
+const SnapshotPayload = Schema.Struct({
+ status: LiveStatus,
+ text: Schema.optionalKey(Schema.NullOr(Schema.String)),
+ isStreaming: Schema.optionalKey(Schema.Boolean),
+ progress: Schema.optionalKey(Schema.NullOr(Schema.Number)),
+ error: Schema.optionalKey(Schema.NullOr(Schema.String)),
+ steps: Schema.optionalKey(Schema.Array(Schema.Unknown)),
+})
+const decodeSnapshot = Schema.decodeUnknownOption(SnapshotPayload)
+
+const progressOf = (payload: unknown) =>
+ Schema.decodeUnknownOption(Schema.Struct({ progress: Schema.Number }))(payload).pipe(
+ Option.map((decoded) => decoded.progress),
+ )
+const fullTextOf = (payload: unknown) =>
+ Schema.decodeUnknownOption(Schema.Struct({ fullText: Schema.String }))(payload).pipe(
+ Option.map((decoded) => decoded.fullText),
+ )
+const textOf = (payload: unknown) =>
+ Schema.decodeUnknownOption(Schema.Struct({ text: Schema.String }))(payload).pipe(
+ Option.map((decoded) => decoded.text),
+ )
+const errorOf = (payload: unknown) =>
+ Schema.decodeUnknownOption(Schema.Struct({ error: Schema.Unknown }))(payload).pipe(
+ Option.map((decoded) => decoded.error),
+ )
+
+/** Each actor event as a `LiveEvent`, or nothing when the payload does not decode. */
+const eventDecoders: ReadonlyArray Option.Option]> = [
+ ["started", () => Option.some(LiveEvent.Started())],
+ ["progress", (payload) => progressOf(payload).pipe(Option.map((progress) => LiveEvent.Progress({ progress })))],
+ ["textChunk", (payload) => fullTextOf(payload).pipe(Option.map((fullText) => LiveEvent.TextChunk({ fullText })))],
+ ["textUpdate", (payload) => textOf(payload).pipe(Option.map((text) => LiveEvent.TextUpdate({ text })))],
+ ["streamEnd", (payload) => textOf(payload).pipe(Option.map((text) => LiveEvent.StreamEnd({ text })))],
+ ["completed", () => Option.some(LiveEvent.Completed())],
+ [
+ "failed",
+ (payload) =>
+ errorOf(payload).pipe(Option.map((error) => LiveEvent.Failed({ error: normalizeActorError(error) }))),
+ ],
+ ["stepAdded", () => Option.some(LiveEvent.StepAdded())],
+]
+
+const snapshotEvent = (state: unknown): Option.Option =>
+ decodeSnapshot(state).pipe(
+ Option.map((snapshot) =>
+ LiveEvent.Snapshot({
+ state: {
+ status: snapshot.status,
+ text: snapshot.text ?? "",
+ isStreaming: snapshot.isStreaming ?? false,
+ progress: snapshot.progress ?? null,
+ error: snapshot.error ?? null,
+ stepCount: snapshot.steps?.length ?? 0,
+ },
+ }),
+ ),
+ )
+
+/**
+ * One message's actor connection: `getOrCreate([messageId], { params: { token } }).connect()`, the
+ * state fetched on open, then every event. Disposed when the Stream ends (the row leaves the page).
+ */
+export const liveEventStream = (messageId: MessageId): Stream.Stream =>
+ Stream.callback((queue) =>
+ Effect.acquireRelease(
+ Effect.promise(async () => {
+ const { rivetClient, getAccessToken } = await import("~/lib/rivet-client")
+ const token = await getAccessToken()
+ const offer = (event: Option.Option) => {
+ if (Option.isSome(event))
+ Queue.offerUnsafe(queue, Message.ReceivedLiveEvent({ messageId, event: event.value }))
+ }
+ const connection = rivetClient.message.getOrCreate([messageId], { params: { token: token ?? "" } }).connect()
+ connection.onOpen(() => {
+ void connection.getState().then((state: unknown) => offer(snapshotEvent(state)))
+ })
+ for (const [name, decode] of eventDecoders)
+ connection.on(name, (payload: unknown) => offer(decode(payload)))
+ return connection
+ }),
+ (connection) => Effect.promise(() => connection.dispose()),
+ ).pipe(Effect.flatMap(() => Effect.never)),
+ )
diff --git a/apps/web-foldkit/src/chat/markdown/leaves.ts b/apps/web-foldkit/src/chat/markdown/leaves.ts
new file mode 100644
index 000000000..49f12d505
--- /dev/null
+++ b/apps/web-foldkit/src/chat/markdown/leaves.ts
@@ -0,0 +1,103 @@
+import type { Html, HtmlBuilder } from "foldkit/html"
+import {
+ type CustomDescendant,
+ type CustomElement,
+ markdownLeafClassName,
+ type ViewerLeaf,
+} from "~/components/chat/slate-editor/slate-message-viewer-model"
+
+/** Slate's text and leaf DOM for the read-only viewer (`String`, `Leaf`, `MarkdownLeaf`). */
+
+export type TextNode = { readonly text: string }
+
+export const isText = (node: CustomDescendant): node is CustomDescendant & TextNode => "text" in node
+
+export const childrenOf = (element: CustomElement): ReadonlyArray =>
+ "children" in element ? (element.children as CustomDescendant[]) : []
+
+export const nodeString = (node: CustomDescendant): string =>
+ isText(node)
+ ? node.text
+ : childrenOf(node as CustomElement)
+ .map(nodeString)
+ .join("")
+
+export interface LeafContext {
+ readonly isLast: boolean
+ readonly isLastText: boolean
+ readonly isBlockEmpty: boolean
+}
+
+/** slate-react `String`: zero-width placeholders for empty leaves, a trailing newline kept visible. */
+const leafString = (h: HtmlBuilder, leaf: ViewerLeaf, context: LeafContext): Html => {
+ if (leaf.text === "")
+ return context.isLastText && context.isBlockEmpty
+ ? h.span(
+ [h.Attribute("data-slate-zero-width", "n"), h.Attribute("data-slate-length", "0")],
+ ["", h.br([])],
+ )
+ : h.span([h.Attribute("data-slate-zero-width", "z"), h.Attribute("data-slate-length", "0")], [""])
+ const text = context.isLast && leaf.text.endsWith("\n") ? `${leaf.text}\n` : leaf.text
+ return h.span([h.Attribute("data-slate-string", "true")], [text])
+}
+
+const LINK_CLASS = "cursor-pointer text-primary underline hover:text-primary-hover"
+
+/** `MarkdownLeaf` in viewer mode. */
+const leafView = (h: HtmlBuilder, leaf: ViewerLeaf, content: Html): Html => {
+ const leafAttribute = h.Attribute("data-slate-leaf", "true")
+ if (leaf.type === "emoji" && leaf.shortcode)
+ return h.span([leafAttribute, h.Attribute("role", "button"), h.Attribute("tabindex", "0")], [content])
+ if (
+ !leaf.token &&
+ leaf.type === "link" &&
+ typeof leaf.url === "string" &&
+ typeof leaf.linkText === "string"
+ )
+ return h.a(
+ [
+ leafAttribute,
+ h.Href(leaf.url),
+ h.Attribute("target", "_blank"),
+ h.Attribute("rel", "noopener noreferrer"),
+ h.Class(LINK_CLASS),
+ ],
+ [leaf.linkText],
+ )
+ if (!leaf.token && leaf.type === "url" && typeof leaf.url === "string")
+ return h.a(
+ [
+ leafAttribute,
+ h.Href(leaf.url),
+ h.Attribute("target", "_blank"),
+ h.Attribute("rel", "noopener noreferrer"),
+ h.Class(LINK_CLASS),
+ ],
+ [content],
+ )
+ return h.span([leafAttribute, h.Attribute("class", markdownLeafClassName(leaf, "viewer"))], [content])
+}
+
+/** A Slate text node: one `data-slate-node="text"` span holding its decorated leaves. */
+export const textNodeView = (
+ h: HtmlBuilder,
+ leaves: ReadonlyArray,
+ context: Omit,
+): Html =>
+ h.span(
+ [h.Attribute("data-slate-node", "text")],
+ leaves.map((leaf, index) =>
+ leafView(
+ h,
+ leaf,
+ leafString(h, leaf, {
+ ...context,
+ isLast: context.isLastText && index === leaves.length - 1,
+ }),
+ ),
+ ),
+ )
+
+/** The `[{ text: "" }]` child every void inline element renders. */
+export const voidChildView = (h: HtmlBuilder): Html =>
+ textNodeView(h, [{ text: "" }], { isLastText: true, isBlockEmpty: true })
diff --git a/apps/web-foldkit/src/chat/markdown/markdown-view.ts b/apps/web-foldkit/src/chat/markdown/markdown-view.ts
new file mode 100644
index 000000000..38caca9bf
--- /dev/null
+++ b/apps/web-foldkit/src/chat/markdown/markdown-view.ts
@@ -0,0 +1,270 @@
+import type { Html, HtmlBuilder } from "foldkit/html"
+import {
+ codeBlockLeaves,
+ type CustomDescendant,
+ type CustomElement,
+ deserializeFromMarkdown,
+ isEmojiOnly,
+ viewerLeaves,
+} from "~/components/chat/slate-editor/slate-message-viewer-model"
+import { cn } from "~/lib/utils"
+import { cx } from "~/utils/cx"
+import { IconCopy } from "../../icons"
+import { childrenOf, isText, nodeString, type TextNode, textNodeView, voidChildView } from "./leaves"
+
+/**
+ * Port of `SlateMessageViewer` as a pure view: the same parse (`deserializeFromMarkdown`), the same
+ * decorations and leaf classes, and the DOM a read-only Slate `Editable` produces, with no editor
+ * instance per message (plan §3.4).
+ */
+
+/** What the viewer resolves outside the markdown: mention names and custom emoji images. */
+export interface MarkdownRefs {
+ readonly mentions: ReadonlyArray<{ readonly userId: string; readonly name: string }>
+ readonly emojis: ReadonlyArray<{ readonly name: string; readonly imageUrl: string }>
+}
+
+export const NO_REFS: MarkdownRefs = { mentions: [], emojis: [] }
+
+type Path = ReadonlyArray
+
+const textView = (h: HtmlBuilder, text: TextNode, path: Path, parent: CustomElement, index: number) =>
+ textNodeView(h, viewerLeaves(text, [...path], parent), {
+ isLastText: index === childrenOf(parent).length - 1,
+ isBlockEmpty: nodeString(parent) === "",
+ })
+
+const headingClass = (level: number) =>
+ level === 1
+ ? cx("font-semibold tracking-tight", "mt-4 mb-2 text-2xl first:mt-0")
+ : level === 2
+ ? cx("font-semibold tracking-tight", "mt-3 mb-1.5 text-xl first:mt-0")
+ : cx("font-semibold tracking-tight", "mt-2 mb-1 text-lg first:mt-0")
+
+const COLLAPSE_THRESHOLD = 15
+
+/** `CodeBlockElement` with controls; collapsing past 15 lines starts collapsed. */
+const codeBlockView = (h: HtmlBuilder, element: CustomElement, path: Path): Html => {
+ const children = childrenOf(element)
+ const codeText = children.map((child) => (isText(child) ? child.text : "")).join("")
+ const language = ("language" in element && element.language) || "plaintext"
+ const lineCount = codeText.split("\n").length
+ const isCollapsible = lineCount > COLLAPSE_THRESHOLD
+ const code = children.map((child, index) =>
+ isText(child)
+ ? textNodeView(h, codeBlockLeaves(element, [...path], child, [...path, index]), {
+ isLastText: index === children.length - 1,
+ isBlockEmpty: codeText === "",
+ })
+ : h.span([], []),
+ )
+ return h.div(
+ [h.Class("group relative my-2"), h.Attribute("data-slate-node", "element")],
+ [
+ h.div(
+ [h.Class("absolute top-2 right-2 z-10 flex items-center gap-2")],
+ [
+ ...(language !== "plaintext"
+ ? [
+ h.span(
+ [
+ h.Class(
+ "rounded-md bg-info-subtle px-2 py-0.5 font-medium text-info-subtle-fg text-xs",
+ ),
+ ],
+ [language],
+ ),
+ ]
+ : []),
+ h.span(
+ [
+ h.Attribute("role", "button"),
+ h.Attribute("tabindex", "0"),
+ h.Class(
+ "cursor-pointer rounded bg-accent-9/10 p-1.5 opacity-0 transition-opacity hover:bg-accent-9/20 group-hover:opacity-100",
+ ),
+ h.Attribute("contenteditable", "false"),
+ h.Attribute("style", ""),
+ h.Attribute("title", "Copy code"),
+ ],
+ [IconCopy(h, { className: "size-3.5", attributes: { "data-slot": "icon" } })],
+ ),
+ ],
+ ),
+ h.pre(
+ [
+ h.Class(
+ cx(
+ "overflow-x-auto whitespace-pre-wrap rounded-lg bg-muted p-4 pr-24 font-mono text-sm",
+ isCollapsible && "max-h-80 overflow-hidden",
+ ),
+ ),
+ ],
+ [h.code([], code)],
+ ),
+ ],
+ )
+}
+
+const MENTION_CLASS =
+ "inline-block cursor-pointer rounded bg-primary/10 px-1 py-0.5 align-baseline font-medium text-primary transition-colors"
+
+/** `MentionElement`, interactive (a popover trigger). */
+const mentionView = (h: HtmlBuilder, element: CustomElement, refs: MarkdownRefs): Html => {
+ const userId = "userId" in element ? String(element.userId) : ""
+ const displayName = "displayName" in element ? String(element.displayName) : ""
+ const node = h.Attribute("data-slate-node", "element")
+ if (userId === "channel" || userId === "here")
+ return h.span(
+ [node, h.Attribute("contenteditable", "false"), h.Class(cn(MENTION_CLASS))],
+ ["@", displayName, voidChildView(h)],
+ )
+ const fullName = refs.mentions.find((mention) => mention.userId === userId)?.name ?? displayName
+ return h.button(
+ [
+ h.Attribute("aria-expanded", "false"),
+ h.Class(
+ cn(
+ "inline-block cursor-pointer rounded bg-primary/10 px-1 py-0.5 align-baseline font-medium text-primary outline-hidden transition-colors hover:bg-primary/20",
+ ),
+ ),
+ h.Attribute("data-rac", ""),
+ h.Attribute("data-react-aria-pressable", "true"),
+ node,
+ h.Attribute("tabindex", "0"),
+ h.Attribute("type", "button"),
+ ],
+ ["@", fullName, voidChildView(h)],
+ )
+}
+
+/** `CustomEmojiElement`: the org's image, or `:name:` when the org has no such emoji. */
+const customEmojiView = (h: HtmlBuilder, element: CustomElement, refs: MarkdownRefs): Html => {
+ const name = "name" in element ? String(element.name) : ""
+ const node = h.Attribute("data-slate-node", "element")
+ const imageUrl = refs.emojis.find((emoji) => emoji.name === name)?.imageUrl
+ if (!imageUrl)
+ return h.span(
+ [node, h.Attribute("contenteditable", "false"), h.Class("inline-block align-text-bottom")],
+ [`:${name}:`, voidChildView(h)],
+ )
+ return h.span(
+ [
+ node,
+ h.Attribute("contenteditable", "false"),
+ h.Attribute("role", "button"),
+ h.Attribute("style", ""),
+ h.Attribute("tabindex", "0"),
+ ],
+ [
+ h.img([
+ h.Attribute("src", imageUrl),
+ h.Attribute("alt", `:${name}:`),
+ h.Class("inline-block size-5 align-text-bottom"),
+ ]),
+ voidChildView(h),
+ ],
+ )
+}
+
+const elementView = (h: HtmlBuilder, element: CustomElement, path: Path, refs: MarkdownRefs): Html => {
+ if (element.type === "code-block") return codeBlockView(h, element, path)
+ if (element.type === "mention") return mentionView(h, element, refs)
+ if (element.type === "custom-emoji") return customEmojiView(h, element, refs)
+ const children = childrenOf(element).map((child, index) =>
+ isText(child)
+ ? textView(h, child, [...path, index], element, index)
+ : elementView(h, child as CustomElement, [...path, index], refs),
+ )
+ const node = h.Attribute("data-slate-node", "element")
+ if (element.type === "paragraph") return h.p([node, h.Class("my-0 last:empty:hidden")], children)
+ if (element.type === "list-item") return h.li([node, h.Class("my-0.5 ml-4")], children)
+ if (element.type === "subtext") return h.p([node, h.Class("my-0 text-muted-fg text-xs")], children)
+ if (element.type === "blockquote")
+ return h.blockquote(
+ [node, h.Class("relative my-1 pl-4 italic")],
+ [
+ h.span(
+ [
+ h.Class("absolute top-0 left-0 h-full w-1 rounded-xs bg-primary"),
+ h.Attribute("aria-hidden", "true"),
+ ],
+ [],
+ ),
+ ...children,
+ ],
+ )
+ if (element.type === "heading") {
+ const tag = element.level === 1 ? h.h1 : element.level === 2 ? h.h2 : h.h3
+ return tag([node, h.Class(headingClass(element.level))], children)
+ }
+ if (element.type === "table") return tableView(h, element, children)
+ if (element.type === "table-row") return h.tr([node], children)
+ if (element.type === "table-cell") {
+ const align = "align" in element ? element.align : undefined
+ const isHeader = "header" in element && element.header
+ const cellClasses = cx(
+ "border border-border px-3 py-2",
+ align === "center" ? "text-center" : align === "right" ? "text-right" : "text-left",
+ )
+ return isHeader
+ ? h.th([node, h.Class(cx(cellClasses, "font-medium"))], children)
+ : h.td([node, h.Class(cellClasses)], children)
+ }
+ return h.p([node], children)
+}
+
+/** `TableElement`: the first row goes in a `thead` when it has header cells. */
+const tableView = (h: HtmlBuilder, element: CustomElement, children: ReadonlyArray): Html => {
+ const firstRow = childrenOf(element)[0]
+ const hasHeader =
+ firstRow !== undefined &&
+ !isText(firstRow) &&
+ childrenOf(firstRow as CustomElement).some((cell) => "header" in cell && cell.header)
+ return h.div(
+ [h.Attribute("data-slate-node", "element"), h.Class("my-2 overflow-x-auto")],
+ [
+ h.table(
+ [h.Class("w-full border-collapse text-sm")],
+ hasHeader
+ ? [h.thead([h.Class("bg-muted")], children.slice(0, 1)), h.tbody([], children.slice(1))]
+ : [h.tbody([], [...children])],
+ ),
+ ],
+ )
+}
+
+/** `SlateMessageViewer`. */
+export const markdownView = (h: HtmlBuilder, content: string, refs: MarkdownRefs = NO_REFS): Html => {
+ const value: ReadonlyArray = deserializeFromMarkdown(content)
+ return h.div(
+ [h.Class(cx("w-full", undefined))],
+ [
+ h.div(
+ [
+ h.Class(
+ cx(
+ "wrap-break-word w-full cursor-text select-text whitespace-pre-wrap",
+ isEmojiOnly(content) ? "text-2xl" : "text-base",
+ "[&_strong]:font-bold",
+ ),
+ ),
+ h.Attribute("contenteditable", "false"),
+ h.Attribute("data-slate-editor", "true"),
+ h.Attribute("data-slate-node", "value"),
+ h.Attribute(
+ "style",
+ "position: relative; white-space: pre-wrap; overflow-wrap: break-word;",
+ ),
+ h.Attribute("translate", "no"),
+ h.Attribute("zindex", "-1"),
+ ],
+ value.map((node, index) =>
+ isText(node)
+ ? h.span([], [node.text])
+ : elementView(h, node as CustomElement, [index], refs),
+ ),
+ ),
+ ],
+ )
+}
diff --git a/apps/web-foldkit/src/chat/message/content.ts b/apps/web-foldkit/src/chat/message/content.ts
new file mode 100644
index 000000000..4d0cade5a
--- /dev/null
+++ b/apps/web-foldkit/src/chat/message/content.ts
@@ -0,0 +1,122 @@
+import type { MessageEmbed } from "@hazel/domain/models"
+import {
+ extractGitHubInfo,
+ extractLinearIssueKey,
+ extractUrls,
+ isGifUrl,
+ isGitHubPRUrl,
+ isLinearIssueUrl,
+ isTweetUrl,
+ isYoutubeUrl,
+} from "~/components/link-preview.utils"
+import {
+ type CustomDescendant,
+ deserializeFromMarkdown,
+} from "~/components/chat/slate-editor/slate-markdown-serializer"
+
+// The serializer alone (no Prism, no DOM), so row derivation can run in `update` and in tests.
+const nodeString = (node: CustomDescendant): string =>
+ "text" in node ? node.text : "children" in node ? node.children.map(nodeString).join("") : ""
+
+/** `MessageContent.Provider`'s URL processing: which URLs embed, and the text left to display. */
+
+export interface ProcessedUrls {
+ readonly tweetUrls: ReadonlyArray
+ readonly youtubeUrls: ReadonlyArray
+ readonly gifUrls: ReadonlyArray
+ readonly embedUrls: ReadonlyArray
+ readonly otherUrls: ReadonlyArray
+ readonly displayContent: string
+}
+
+const NONE: ReadonlyArray = []
+
+/** URLs in paragraphs only (not code blocks, tables or quotes), deduped. */
+const extractUrlsFromParagraphs = (content: string): ReadonlyArray => {
+ const urls: string[] = []
+ for (const node of deserializeFromMarkdown(content))
+ if (!("text" in node) && node.type === "paragraph")
+ urls.push(...extractUrls(nodeString(node)))
+ return [...new Set(urls)]
+}
+
+const isLinkShareMessage = (content: string, paragraphUrls: ReadonlyArray) => {
+ if (paragraphUrls.length === 0) return false
+ let textOnly = content
+ for (const url of paragraphUrls) textOnly = textOnly.replaceAll(url, "")
+ return textOnly.replace(/\s+/g, " ").trim().length < 300
+}
+
+const githubKey = (url: string) => {
+ const info = extractGitHubInfo(url)
+ return info ? `${info.owner}/${info.repo}/${info.number}` : null
+}
+
+export const processUrls = (
+ content: string,
+ embeds: ReadonlyArray | null,
+): ProcessedUrls => {
+ const paragraphUrls = extractUrlsFromParagraphs(content)
+ if (!isLinkShareMessage(content, paragraphUrls))
+ return {
+ tweetUrls: NONE,
+ youtubeUrls: NONE,
+ gifUrls: NONE,
+ embedUrls: NONE,
+ otherUrls: NONE,
+ displayContent: content,
+ }
+
+ const existingUrls = new Set()
+ const existingLinear = new Set()
+ const existingGitHub = new Set()
+ for (const url of (embeds ?? []).flatMap((embed) => [embed.url, embed.author?.url])) {
+ if (!url) continue
+ existingUrls.add(url)
+ const linearKey = extractLinearIssueKey(url)
+ if (linearKey) existingLinear.add(linearKey)
+ const key = githubKey(url)
+ if (key) existingGitHub.add(key)
+ }
+
+ const unique = paragraphUrls.filter((url) => !existingUrls.has(url))
+ const linear = unique.filter((url) => {
+ if (!isLinearIssueUrl(url)) return false
+ const key = extractLinearIssueKey(url)
+ return key && !existingLinear.has(key)
+ })
+ const github = unique.filter((url) => {
+ if (!isGitHubPRUrl(url)) return false
+ const key = githubKey(url)
+ return key && !existingGitHub.has(key)
+ })
+ const tweetUrls = unique.filter(isTweetUrl)
+ const youtubeUrls = unique.filter(isYoutubeUrl)
+ const gifUrls = unique.filter(isGifUrl)
+ const embedUrls = [...tweetUrls, ...youtubeUrls, ...gifUrls, ...linear, ...github]
+ const otherUrls = unique.filter(
+ (url) =>
+ !isTweetUrl(url) &&
+ !isYoutubeUrl(url) &&
+ !isGifUrl(url) &&
+ !isLinearIssueUrl(url) &&
+ !isGitHubPRUrl(url),
+ )
+ let displayContent = content
+ for (const url of embedUrls) displayContent = displayContent.replace(url, "")
+ return { tweetUrls, youtubeUrls, gifUrls, embedUrls, otherUrls, displayContent: displayContent.trim() }
+}
+
+const processed = new WeakMap