21st.dev 원본

Human Review Panel · Extend UI

섹션 · MIT

섹션 더 보기
ORIGINAL PREVIEW
Human Review Panel · Extend UI 정적 미리보기

갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.

큰 화면으로 보기 새 탭실행 HTML 저장

저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.

SOURCE FILES

원본 코드 읽기

27개 파일

수집한 원본 소스와 실행 안내를 함께 제공합니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.

baseline-example.tsx
파일 저장

"use client";

import { HumanReviewPanel } from "@/components/ui/bounding-box-citations";

export default function Default() {
  return (
    <div className="mx-auto flex w-full max-w-2xl items-center justify-center p-6">
      <div className="w-full overflow-hidden rounded-lg border bg-background shadow-sm">
        <HumanReviewPanel className="!h-[680px]" />
      </div>
    </div>
  );
}
LICENSE
파일 저장

MIT License

Copyright (c) 2026 CrowdView Inc, dba Extend
Portions Copyright (c) 2023 shadcn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
함께 쓰는 파일 25개 보기
author/apps/v4/components/extend/bounding-box-citations.tsx
파일 저장

"use client"

import * as React from "react"
import {
  DataEditor,
  emptyGridSelection,
  GridCellKind,
  TextCellEntry,
  type EditableGridCell,
  type GridCell,
  type GridColumn,
  type GridMouseEventArgs,
  type GridSelection,
  type Item,
  type NumberCell,
  type ProvideEditorComponent,
  type Rectangle,
  type TextCell,
  type Theme,
} from "@glideapps/glide-data-grid"
import { Virtualizer as DiffsVirtualizer } from "@pierre/diffs"
import {
  File,
  MultiFileDiff,
  VirtualizerContext,
  WorkerPoolContextProvider,
  type VirtualFileMetrics,
  type WorkerInitializationRenderOptions,
  type WorkerPoolOptions,
} from "@pierre/diffs/react"
import { flushSync } from "react-dom"

import type { RegistryIconProps } from "@/lib/registry-icon-props"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"
import { IconPlaceholder } from "@/components/icon-placeholder"

import "@glideapps/glide-data-grid/dist/index.css"

function InputNumericGlyph(props: RegistryIconProps) {
  return (
    <IconPlaceholder
      lucide="Hash"
      tabler="IconNumber123"
      hugeicons="InputNumericIcon"
      phosphor="NumpadIcon"
      remixicon="RiHashtag"
      {...props}
    />
  )
}

function InputTextGlyph(props: RegistryIconProps) {
  return (
    <IconPlaceholder
      lucide="TextCursorInput"
      tabler="IconForms"
      hugeicons="InputTextIcon"
      phosphor="TextboxIcon"
      remixicon="RiInputField"
      {...props}
    />
  )
}

function SecondBracketGlyph(props: RegistryIconProps) {
  return (
    <IconPlaceholder
      lucide="Brackets"
      tabler="IconBrackets"
      hugeicons="SecondBracketIcon"
      phosphor="BracketsSquareIcon"
      remixicon="RiBracketsLine"
      {...props}
    />
  )
}

function SourceCodeSquareGlyph(props: RegistryIconProps) {
  return (
    <IconPlaceholder
      lucide="SquareCode"
      tabler="IconSourceCode"
      hugeicons="SourceCodeSquareIcon"
      phosphor="CodeIcon"
      remixicon="RiCodeBlock"
      {...props}
    />
  )
}

function TextCheckGlyph(props: RegistryIconProps) {
  return (
    <IconPlaceholder
      lucide="CaptionsIcon"
      tabler="IconTextCaption"
      hugeicons="TextCheckIcon"
      phosphor="TextTIcon"
      remixicon="RiTextWrap"
      {...props}
    />
  )
}

export type JsonPrimitive = string | number | boolean | null
export type JsonValue = JsonPrimitive | JsonObject | JsonArray
export type JsonObject = { [key: string]: JsonValue }
export type JsonArray = JsonValue[]
export type SchemaPropertyType =
  | "string"
  | "number"
  | "integer"
  | "boolean"
  | "object"
  | "array"

export type ReviewFieldSchema = {
  type: SchemaPropertyType
  title?: string
  description?: string
  enum?: Array<string | number>
  properties?: Record<string, ReviewFieldSchema>
  items?: ReviewFieldSchema
}

export type HighlightArea = {
  left: number
  top: number
  width: number
  height: number
}

export type ReviewField = {
  key: string
  schema: ReviewFieldSchema
  actual: JsonValue
  expected: JsonValue
  location?: ReviewLocation
  metadataPath?: string
}

export type ReviewLocation = {
  page: number
  area: HighlightArea
}

export type ReviewCitation = {
  page: number
  polygon?: Array<{ x: number; y: number }>
  pageWidth: number
  pageHeight: number
}

export type ReviewMetadataEntry = {
  citations?: ReviewCitation[]
}

function ScrollAreaVirtualizer({
  children,
  className,
  contentClassName,
  contentStyle,
  scrollFade = true,
}: {
  children: React.ReactNode
  className?: string
  contentClassName?: string
  contentStyle?: React.CSSProperties
  scrollFade?: boolean
}) {
  const [virtualizer] = React.useState(() =>
    typeof window !== "undefined" ? new DiffsVirtualizer() : undefined
  )
  const viewportRef = React.useRef<HTMLDivElement | null>(null)
  const contentRef = React.useRef<HTMLDivElement | null>(null)
  const syncVirtualizer = React.useCallback(() => {
    if (!virtualizer) return

    const viewport = viewportRef.current
    const content = contentRef.current

    if (viewport && content) {
      virtualizer.setup(viewport, content)
      return
    }

    virtualizer.cleanUp()
  }, [virtualizer])
  const setViewportRef = React.useCallback(
    (node: HTMLDivElement | null) => {
      viewportRef.current = node
      syncVirtualizer()
    },
    [syncVirtualizer]
  )
  const setContentRef = React.useCallback(
    (node: HTMLDivElement | null) => {
      contentRef.current = node
      syncVirtualizer()
    },
    [syncVirtualizer]
  )

  React.useEffect(() => {
    return () => virtualizer?.cleanUp()
  }, [virtualizer])

  return (
    <VirtualizerContext.Provider value={virtualizer}>
      <ScrollArea
        className={className}
        scrollFade={scrollFade}
        scrollbarOverflowOnly
        viewportRef={setViewportRef}
      >
        <div
          ref={setContentRef}
          className={contentClassName}
          style={contentStyle}
        >
          {children}
        </div>
      </ScrollArea>
    </VirtualizerContext.Provider>
  )
}

const REVIEW_HIGHLIGHT_STYLE =
  "border-blue-500/70 bg-blue-500/12 shadow-[0_4px_16px_rgb(59_130_246_/_10%)]"

const CODE_FILE_THEME = {
  "--diffs-light-bg": "var(--color-code)",
  "--diffs-dark-bg": "var(--color-code)",
  "--diffs-light": "var(--color-code-foreground)",
  "--diffs-dark": "var(--color-code-foreground)",
  "--diffs-bg-context-override": "var(--color-code)",
  "--diffs-bg-context-gutter-override": "var(--color-code)",
  "--diffs-bg-buffer-override": "var(--color-code)",
  "--diffs-fg-number-override": "var(--color-muted-foreground)",
  "--diffs-font-size": "0.8rem",
  "--diffs-line-height": "1.625",
} as React.CSSProperties

const CODE_FONT_SIZE_PX = 12.8
const CODE_LINE_HEIGHT_PX = CODE_FONT_SIZE_PX * 1.625

const CODE_VIRTUAL_FILE_METRICS = {
  hunkLineCount: 50,
  lineHeight: CODE_LINE_HEIGHT_PX,
  diffHeaderHeight: 44,
  spacing: 8,
  paddingTop: 0,
  paddingBottom: 8,
} satisfies VirtualFileMetrics

const CODE_HIGHLIGHTER_OPTIONS = {
  theme: {
    light: "pierre-light-soft",
    dark: "pierre-dark-soft",
  },
  langs: ["json"],
} satisfies WorkerInitializationRenderOptions

const CODE_WORKER_POOL_OPTIONS = {
  workerFactory: () =>
    new Worker(new URL("@pierre/diffs/worker/worker.js", import.meta.url), {
      type: "module",
    }),
} satisfies WorkerPoolOptions

function readIsDarkTheme() {
  return (
    typeof document !== "undefined" &&
    document.documentElement.classList.contains("dark")
  )
}

// A single shared MutationObserver backs every consumer. Each grid previously
// created its own observer (two, via useHumanReviewGridTheme), so opening a
// nested array view spun up and tore down several observers at once.
const darkThemeListeners = new Set<(isDark: boolean) => void>()
let darkThemeObserver: MutationObserver | null = null
let sharedIsDarkTheme = false

function ensureDarkThemeObserver() {
  if (
    darkThemeObserver ||
    typeof document === "undefined" ||
    typeof MutationObserver === "undefined"
  ) {
    return
  }

  sharedIsDarkTheme = readIsDarkTheme()
  darkThemeObserver = new MutationObserver(() => {
    const nextIsDark = readIsDarkTheme()
    if (nextIsDark === sharedIsDarkTheme) return

    sharedIsDarkTheme = nextIsDark
    darkThemeListeners.forEach((listener) => listener(nextIsDark))
  })
  darkThemeObserver.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ["class"],
  })
}

function subscribeToDarkTheme(listener: () => void) {
  ensureDarkThemeObserver()
  darkThemeListeners.add(listener)
  return () => {
    darkThemeListeners.delete(listener)
    if (darkThemeListeners.size === 0 && darkThemeObserver) {
      darkThemeObserver.disconnect()
      darkThemeObserver = null
    }
  }
}

function useIsDarkTheme() {
  return React.useSyncExternalStore(
    subscribeToDarkTheme,
    readIsDarkTheme,
    () => false
  )
}

function useHumanReviewGridTheme() {
  const isDark = useIsDarkTheme()

  return React.useMemo<Partial<Theme>>(
    () => ({
      accentColor: isDark ? "rgb(96, 165, 250)" : "rgb(37, 99, 235)",
      accentLight: isDark ? "rgba(29, 78, 216, 0.15)" : "rgb(219, 234, 254)",
      accentFg: "rgb(255, 255, 255)",
      textDark: isDark ? "rgb(229, 229, 229)" : "rgb(23, 23, 23)",
      textMedium: isDark ? "rgb(163, 163, 163)" : "rgb(82, 82, 82)",
      textLight: isDark ? "rgb(115, 115, 115)" : "rgb(163, 163, 163)",
      textBubble: isDark ? "rgb(245, 245, 245)" : "rgb(23, 23, 23)",
      textHeader: isDark ? "rgb(245, 245, 245)" : "rgb(23, 23, 23)",
      textGroupHeader: isDark ? "rgb(163, 163, 163)" : "rgb(82, 82, 82)",
      bgCell: isDark ? "rgb(10, 10, 10)" : "rgb(255, 255, 255)",
      bgCellMedium: isDark ? "rgb(23, 23, 23)" : "rgb(250, 250, 250)",
      bgHeader: isDark ? "rgb(23, 23, 23)" : "rgb(250, 250, 250)",
      bgHeaderHasFocus: isDark ? "rgb(38, 38, 38)" : "rgb(245, 245, 245)",
      bgHeaderHovered: isDark ? "rgb(38, 38, 38)" : "rgb(245, 245, 245)",
      borderColor: isDark ? "rgb(38, 38, 38)" : "rgb(229, 229, 229)",
      horizontalBorderColor: isDark ? "rgb(38, 38, 38)" : "rgb(229, 229, 229)",
      cellHorizontalPadding: 8,
      cellVerticalPadding: 3,
      headerIconSize: 18,
      baseFontStyle: "13px",
      headerFontStyle: "600 13px",
      markerFontStyle: "11px",
      fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
      editorFontSize: "13px",
    }),
    [isDark]
  )
}

export const REVIEW_FIELDS: ReviewField[] = [
  {
    key: "statement_period",
    schema: {
      type: "string",
      title: "Statement period",
      description: "Date range covered by the bank statement.",
    },
    actual: "Jan 1-31, 2026",
    expected: "January 1-31, 2026",
    location: {
      page: 1,
      area: { left: 31, top: 30, width: 40, height: 5.8 },
    },
  },
  {
    key: "transactions",
    schema: {
      type: "array",
      title: "Transactions",
      description: "Posted account activity during the statement period.",
      items: {
        type: "object",
        properties: {
          date: {
            type: "string",
            title: "Date",
          },
          description: {
            type: "string",
            title: "Description",
          },
          amount: {
            type: "number",
            title: "Amount",
          },
          category: {
            type: "string",
            title: "Category",
          },
          merchant: {
            type: "object",
            title: "Merchant",
            properties: {
              name: {
                type: "string",
                title: "Name",
              },
              city: {
                type: "string",
                title: "City",
              },
              risk_level: {
                type: "string",
                title: "Risk level",
              },
            },
          },
          tags: {
            type: "array",
            title: "Tags",
            items: {
              type: "string",
              title: "Tag",
            },
          },
        },
      },
    },
    actual: [
      {
        date: "2026-01-03",
        description: "ACH CREDIT PAYROLL",
        amount: 4250,
        category: "Deposit",
        merchant: {
          name: "Acme Payroll",
          city: "New York",
          risk_level: "low",
        },
        tags: ["payroll", "recurring"],
      },
      {
        date: "2026-01-12",
        description: "POS PURCHASE GROCERY MART",
        amount: -86.42,
        category: "Debit",
        merchant: {
          name: "Grocery Mart",
          city: "Brooklyn",
          risk_level: "medium",
        },
        tags: ["card", "groceries"],
      },
    ],
    expected: [
      {
        date: "2026-01-03",
        description: "ACH CREDIT PAYROLL",
        amount: 4250,
        category: "Deposit",
        merchant: {
          name: "Acme Payroll",
          city: "New York",
          risk_level: "low",
        },
        tags: ["payroll", "recurring"],
      },
      {
        date: "2026-01-12",
        description: "POS PURCHASE GROCERY MART",
        amount: -68.42,
        category: "Debit",
        merchant: {
          name: "Grocery Mart",
          city: "Brooklyn",
          risk_level: "low",
        },
        tags: ["card", "groceries", "needs_review"],
      },
    ],
    location: {
      page: 1,
      area: { left: 13.5, top: 66, width: 73.5, height: 7.5 },
    },
  },
  {
    key: "ending_balance",
    schema: {
      type: "number",
      title: "Ending balance",
      description: "Final account balance at the end of the statement period.",
    },
    actual: 12840.18,
    expected: 12858.18,
    location: {
      page: 1,
      area: { left: 13.5, top: 66, width: 73.5, height: 7.5 },
    },
  },
  {
    key: "overdraft_protection_enabled",
    schema: {
      type: "boolean",
      title: "Overdraft protection enabled",
      description: "Whether overdraft protection is enabled for the account.",
    },
    actual: false,
    expected: true,
    location: {
      page: 2,
      area: { left: 9.5, top: 12, width: 81, height: 11.5 },
    },
  },
  {
    key: "account_details",
    schema: {
      type: "object",
      title: "Account details",
      description: "Account owner and identifying details from the statement.",
      properties: {
        holder_name: {
          type: "string",
          title: "Holder name",
        },
        account_last_four: {
          type: "string",
          title: "Account last four",
        },
        account_type: {
          type: "string",
          title: "Account type",
        },
        mailing_address: {
          type: "object",
          title: "Mailing address",
          properties: {
            line_1: {
              type: "string",
              title: "Line 1",
            },
            city: {
              type: "string",
              title: "City",
            },
            state: {
              type: "string",
              title: "State",
            },
          },
        },
        linked_accounts: {
          type: "array",
          title: "Linked accounts",
          items: {
            type: "object",
            properties: {
              nickname: {
                type: "string",
                title: "Nickname",
              },
              last_four: {
                type: "string",
                title: "Last four",
              },
            },
          },
        },
      },
    },
    actual: {
      holder_name: "Jordan Lee",
      account_last_four: "4821",
      account_type: "Checking",
      mailing_address: {
        line_1: "42 Market Street",
        city: "Brooklyn",
        state: "NY",
      },
      linked_accounts: [
        {
          nickname: "Operations reserve",
          last_four: "1842",
        },
      ],
    },
    expected: {
      holder_name: "Jordan Lee",
      account_last_four: "4821",
      account_type: "Premier Checking",
      mailing_address: {
        line_1: "42 Market Street",
        city: "New York",
        state: "NY",
      },
      linked_accounts: [
        {
          nickname: "Operations reserve",
          last_four: "1842",
        },
        {
          nickname: "Payroll sweep",
          last_four: "9174",
        },
      ],
    },
  },
]

function getCitationLocation(
  citation: ReviewCitation
): ReviewLocation | undefined {
  const polygon = citation.polygon
  if (!polygon?.length || !citation.pageWidth || !citation.pageHeight) {
    return undefined
  }

  const xs = polygon.map((point) => point.x)
  const ys = polygon.map((point) => point.y)
  const left = Math.min(...xs)
  const top = Math.min(...ys)
  const right = Math.max(...xs)
  const bottom = Math.max(...ys)

  return {
    page: citation.page,
    area: {
      left: (left / citation.pageWidth) * 100,
      top: (top / citation.pageHeight) * 100,
      width: ((right - left) / citation.pageWidth) * 100,
      height: ((bottom - top) / citation.pageHeight) * 100,
    },
  }
}

export function getMetadataLocation(
  metadata: Record<string, ReviewMetadataEntry> | undefined,
  metadataPath: string | undefined
) {
  if (!metadata || !metadataPath) return undefined

  const citation = metadata[metadataPath]?.citations?.find(
    (candidate) => candidate.polygon?.length
  )

  return citation ? getCitationLocation(citation) : undefined
}

export function getReviewFieldLocation(
  field: ReviewField | undefined,
  resolveLocation?: (metadataPath: string) => ReviewLocation | undefined
) {
  if (!field) return undefined

  return (
    field.location ??
    resolveLocation?.(field.metadataPath ?? field.key) ??
    undefined
  )
}

export function getReviewLocationKey(location: ReviewLocation | undefined) {
  if (!location) return null

  const { area } = location
  return [location.page, area.left, area.top, area.width, area.height].join(":")
}

function valuesFromFields(
  fields: ReviewField[],
  valueKey: "actual" | "expected"
) {
  return fields.reduce<JsonObject>((values, field) => {
    values[field.key] = field[valueKey]
    return values
  }, {})
}

function formatJson(value: unknown) {
  return JSON.stringify(value, null, 2)
}

function isJsonObject(value: JsonValue): value is JsonObject {
  return typeof value === "object" && value !== null && !Array.isArray(value)
}

function isJsonArray(value: JsonValue): value is JsonArray {
  return Array.isArray(value)
}

function getObjectValue(value: JsonValue, key: string): JsonValue {
  if (!isJsonObject(value)) return null
  return value[key] ?? null
}

function setObjectValue(
  value: JsonValue,
  key: string,
  childValue: JsonValue
): JsonObject {
  return {
    ...(isJsonObject(value) ? value : {}),
    [key]: childValue,
  }
}

function getArrayValue(value: JsonValue): JsonArray {
  return isJsonArray(value) ? value : []
}

function setArrayItemValue(
  value: JsonValue,
  index: number,
  childValue: JsonValue
): JsonArray {
  const nextValue = getArrayValue(value).slice()
  nextValue[index] = childValue
  return nextValue
}

function getPrimitiveValue(value: JsonValue): JsonPrimitive {
  return isJsonObject(value) || isJsonArray(value) ? null : value
}

function jsonValuesEqual(left: JsonValue, right: JsonValue) {
  return formatJson(left) === formatJson(right)
}

export function findReviewField(
  fields: ReviewField[],
  fieldKey: string | undefined
): ReviewField | undefined {
  if (!fieldKey) return undefined

  for (const field of fields) {
    if (field.key === fieldKey) return field

    if (field.schema.type === "object") {
      const childFields = Object.entries(field.schema.properties ?? {}).map(
        ([key, schema]): ReviewField => ({
          key: `${field.key}.${key}`,
          schema,
          actual: getObjectValue(field.actual, key),
          expected: getObjectValue(field.expected, key),
          metadataPath: `${field.metadataPath ?? field.key}.${key}`,
        })
      )
      const childField = findReviewField(childFields, fieldKey)
      if (childField) return childField
    }
  }

  return undefined
}

function formatValue(value: JsonValue) {
  if (value === null) return "NULL"
  if (isJsonObject(value) || isJsonArray(value)) return formatJson(value)
  if (typeof value === "boolean") return value ? "true" : "false"
  return String(value)
}

function areGridRangesEqual(
  left: Readonly<Rectangle> | undefined,
  right: Readonly<Rectangle> | undefined
) {
  return (
    left === right ||
    (left !== undefined &&
      right !== undefined &&
      left.x === right.x &&
      left.y === right.y &&
      left.width === right.width &&
      left.height === right.height)
  )
}

function areGridRangeStacksEqual(
  left: readonly Readonly<Rectangle>[] | undefined,
  right: readonly Readonly<Rectangle>[] | undefined
) {
  if (left === right) return true
  if (!left || !right || left.length !== right.length) return false

  return left.every((range, index) => areGridRangesEqual(range, right[index]))
}

function areGridSelectionsEqual(left: GridSelection, right: GridSelection) {
  const leftCurrent = left.current
  const rightCurrent = right.current

  return (
    leftCurrent?.cell[0] === rightCurrent?.cell[0] &&
    leftCurrent?.cell[1] === rightCurrent?.cell[1] &&
    areGridRangesEqual(leftCurrent?.range, rightCurrent?.range) &&
    areGridRangeStacksEqual(
      leftCurrent?.rangeStack,
      rightCurrent?.rangeStack
    ) &&
    left.columns.equals(right.columns) &&
    left.rows.equals(right.rows)
  )
}

function getGridSelectionRanges(selection: GridSelection | undefined) {
  const current = selection?.current
  if (!current) return []

  return [...(current.rangeStack ?? []), current.range]
}

function areArrayNestedViewsEqual(
  left: ArrayNestedView[],
  right: ArrayNestedView[]
) {
  if (left === right) return true
  if (left.length !== right.length) return false

  return left.every((view, index) => {
    const other = right[index]

    return (
      view.rowIndex === other?.rowIndex &&
      view.columnId === other.columnId &&
      view.title === other.title &&
      view.schema === other.schema &&
      Object.is(view.value, other.value)
    )
  })
}

type HumanReviewOverlayCell = TextCell | NumberCell

type HumanReviewTextOverlayEditorProps = React.ComponentProps<
  ProvideEditorComponent<HumanReviewOverlayCell>
> & {
  overlayOpenRef: React.RefObject<boolean>
  readOnly?: boolean
}

function HumanReviewTextOverlayEditor({
  isHighlighted,
  onFinishedEditing,
  overlayOpenRef,
  readOnly = false,
  validatedSelection,
  value,
}: HumanReviewTextOverlayEditorProps) {
  const initialValue =
    value.kind === GridCellKind.Number ? value.displayData : value.data
  const [entryValue, setEntryValue] = React.useState(initialValue)
  const latestValueRef = React.useRef(initialValue)
  const finishedRef = React.useRef(false)

  const finishEditing = React.useCallback(
    (
      shouldSave: boolean,
      movement: readonly [-1 | 0 | 1, -1 | 0 | 1] = [0, 0]
    ) => {
      if (finishedRef.current) return

      finishedRef.current = true
      overlayOpenRef.current = false

      if (!shouldSave || readOnly) {
        onFinishedEditing(undefined, movement)
        return
      }

      if (value.kind === GridCellKind.Number) {
        const numericValue = Number(latestValueRef.current)

        onFinishedEditing(
          {
            ...value,
            data: Number.isFinite(numericValue) ? numericValue : value.data,
            displayData: latestValueRef.current,
          },
          movement
        )
        return
      }

      onFinishedEditing(
        {
          ...value,
          data: latestValueRef.current,
          displayData: latestValueRef.current,
        },
        movement
      )
    },
    [onFinishedEditing, overlayOpenRef, readOnly, value]
  )

  const handleEntryChange = React.useCallback(
    (event: React.ChangeEvent<HTMLTextAreaElement>) => {
      event.stopPropagation()
      latestValueRef.current = event.target.value
      setEntryValue(event.target.value)
    },
    []
  )
  const handleKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
      event.stopPropagation()

      if (event.key === "Escape") {
        event.preventDefault()
        finishEditing(false)
        return
      }

      if (event.key === "Tab") {
        event.preventDefault()
        finishEditing(true, [event.shiftKey ? -1 : 1, 0])
        return
      }

      if (event.key === "Enter" && !event.shiftKey) {
        event.preventDefault()
        finishEditing(true, [0, 1])
      }
    },
    [finishEditing]
  )

  React.useEffect(() => {
    overlayOpenRef.current = true

    return () => {
      overlayOpenRef.current = false
    }
  }, [overlayOpenRef])

  React.useEffect(() => {
    const handlePointerOutside = (event: PointerEvent | MouseEvent) => {
      const input = document.querySelector<HTMLTextAreaElement>(".gdg-input")
      const overlayRoot = input?.closest(".gdg-clip-region")

      if (!overlayRoot || overlayRoot.contains(event.target as Node | null)) {
        return
      }

      finishEditing(true)
    }

    document.addEventListener("pointerdown", handlePointerOutside, true)
    document.addEventListener("contextmenu", handlePointerOutside, true)

    return () => {
      document.removeEventListener("pointerdown", handlePointerOutside, true)
      document.removeEventListener("contextmenu", handlePointerOutside, true)
    }
  }, [finishEditing])

  return (
    <TextCellEntry
      autoFocus={!readOnly}
      disabled={readOnly}
      highlight={isHighlighted}
      value={entryValue}
      validatedSelection={validatedSelection}
      altNewline
      onChange={handleEntryChange}
      onKeyDown={handleKeyDown}
    />
  )
}

function getFieldIcon(type: SchemaPropertyType) {
  if (type === "number" || type === "integer") return InputNumericGlyph
  if (type === "boolean") return TextCheckGlyph
  if (type === "array") return SecondBracketGlyph
  if (type === "object") return SourceCodeSquareGlyph
  return InputTextGlyph
}

function HumanReviewValueInput({
  readOnly = false,
  schema,
  value,
  onChange,
}: {
  readOnly?: boolean
  schema: ReviewFieldSchema
  value: JsonPrimitive
  onChange: (value: JsonPrimitive) => void
}) {
  if (schema.enum?.length) {
    return (
      <span className="relative inline-flex w-full rounded-lg border border-input bg-background text-sm text-foreground shadow-xs/5 dark:bg-input/32">
        <select
          disabled={readOnly}
          value={value === null ? "" : String(value)}
          onChange={(event) => onChange(event.target.value)}
          className="h-8.5 w-full appearance-none rounded-[inherit] bg-transparent px-3 text-sm outline-none sm:h-7.5"
        >
          {schema.enum.map((option) => (
            <option key={String(option)} value={String(option)}>
              {String(option)}
            </option>
          ))}
        </select>
      </span>
    )
  }

  if (schema.type === "number" || schema.type === "integer") {
    return (
      <Input
        nativeInput
        readOnly={readOnly}
        type="number"
        value={value === null ? "" : String(value)}
        onChange={(event) => {
          const nextValue = event.currentTarget.value
          onChange(nextValue === "" ? null : Number(nextValue))
        }}
      />
    )
  }

  if (schema.type === "boolean") {
    return (
      <div className="grid grid-cols-2 gap-1 rounded-lg bg-muted p-0.5">
        {[true, false].map((option) => (
          <Button
            key={String(option)}
            type="button"
            size="sm"
            variant={value === option ? "outline" : "ghost"}
            className={cn(
              "h-7 shadow-none",
              value === option && "bg-background dark:bg-input"
            )}
            disabled={readOnly}
            onClick={() => onChange(option)}
          >
            {option ? "True" : "False"}
          </Button>
        ))}
      </div>
    )
  }

  return (
    <Input
      nativeInput
      readOnly={readOnly}
      value={value === null ? "" : String(value)}
      onChange={(event) => onChange(event.currentTarget.value)}
    />
  )
}

function getArrayItemSchema(schema: ReviewFieldSchema): ReviewFieldSchema {
  return schema.items ?? { type: "string" }
}

function isComplexSchema(schema: ReviewFieldSchema) {
  return schema.type === "object" || schema.type === "array"
}

function summarizeComplexValue(value: JsonValue) {
  if (isJsonArray(value)) return `${value.length} items`
  if (isJsonObject(value)) return `${Object.keys(value).length} fields`
  return formatValue(value)
}

function getCellValueForArrayColumn(
  rowValue: JsonValue,
  itemSchema: ReviewFieldSchema,
  columnId: string
) {
  if (itemSchema.type === "object") {
    return getObjectValue(rowValue, columnId)
  }

  return rowValue
}

function getCellSchemaForArrayColumn(
  itemSchema: ReviewFieldSchema,
  columnId: string
) {
  if (itemSchema.type === "object") {
    return itemSchema.properties?.[columnId] ?? { type: "string" }
  }

  return itemSchema
}

function applyPrimitiveEdit(
  schema: ReviewFieldSchema,
  value: EditableGridCell
): JsonValue | undefined {
  if (schema.type === "boolean" && value.kind === GridCellKind.Boolean) {
    return value.data
  }

  if (
    (schema.type === "number" || schema.type === "integer") &&
    value.kind === GridCellKind.Number
  ) {
    return value.data ?? null
  }

  if (value.kind === GridCellKind.Text) {
    if (schema.type === "number" || schema.type === "integer") {
      return value.data.trim() === "" ? null : Number(value.data)
    }

    return value.data
  }

  return undefined
}

type ArrayNestedView = {
  rowIndex: number
  columnId: string
  title: string
  schema: ReviewFieldSchema
  value: JsonValue
}

type ArrayReviewSide = "actual" | "expected"

type SyncedArrayNestedView = {
  activeSide: ArrayReviewSide | null
  stack: ArrayNestedView[]
}

type SyncedArraySelection = {
  activeSide: ArrayReviewSide | null
  depth: number
  gridSelection: GridSelection
}

export type HumanReviewTheme = "light" | "dark"

const EMPTY_SYNCED_ARRAY_NESTED_VIEW: SyncedArrayNestedView = {
  activeSide: null,
  stack: [],
}

const EMPTY_SYNCED_ARRAY_SELECTION: SyncedArraySelection = {
  activeSide: null,
  depth: 0,
  gridSelection: emptyGridSelection,
}

function setNestedArrayValue({
  value,
  schema,
  nestedStack,
  nextNestedValue,
}: {
  value: JsonValue
  schema: ReviewFieldSchema
  nestedStack: ArrayNestedView[]
  nextNestedValue: JsonValue
}): JsonValue {
  const [currentView, ...remainingViews] = nestedStack

  if (!currentView) return nextNestedValue

  const itemSchema = getArrayItemSchema(schema)
  const rowValue = getArrayValue(value)[currentView.rowIndex] ?? null
  const cellSchema = getCellSchemaForArrayColumn(
    itemSchema,
    currentView.columnId
  )
  const currentCellValue = getCellValueForArrayColumn(
    rowValue,
    itemSchema,
    currentView.columnId
  )
  const nextCellValue: JsonValue = remainingViews.length
    ? setNestedArrayValue({
        value: currentCellValue,
        schema: cellSchema,
        nestedStack: remainingViews,
        nextNestedValue,
      })
    : nextNestedValue
  const nextRowValue: JsonValue =
    itemSchema.type === "object"
      ? setObjectValue(rowValue, currentView.columnId, nextCellValue)
      : nextCellValue

  return setArrayItemValue(value, currentView.rowIndex, nextRowValue)
}

function getNestedArrayValue({
  value,
  schema,
  nestedStack,
}: {
  value: JsonValue
  schema: ReviewFieldSchema
  nestedStack: ArrayNestedView[]
}): JsonValue {
  const [currentView, ...remainingViews] = nestedStack

  if (!currentView) return value

  const itemSchema = getArrayItemSchema(schema)
  const rowValue = getArrayValue(value)[currentView.rowIndex] ?? null
  const cellSchema = getCellSchemaForArrayColumn(
    itemSchema,
    currentView.columnId
  )
  const cellValue = getCellValueForArrayColumn(
    rowValue,
    itemSchema,
    currentView.columnId
  )

  if (!remainingViews.length) return cellValue

  return getNestedArrayValue({
    value: cellValue,
    schema: cellSchema,
    nestedStack: remainingViews,
  })
}

function HumanReviewArrayValueGrid({
  activeNestedSide = null,
  activeSelectionSide = null,
  label,
  nestedStackBaseDepth = 0,
  readOnly = false,
  schema,
  selectionDepth = 0,
  sharedGridSelection,
  sharedNestedStack,
  value,
  viewSide = "expected",
  metadataPath,
  onChange,
  onGridSelectionChange,
  onLocationHover,
  onNestedStackChange,
  resolveArrayItemMetadataPath,
  resolveLocation,
}: {
  activeNestedSide?: ArrayReviewSide | null
  activeSelectionSide?: ArrayReviewSide | null
  label: string
  nestedStackBaseDepth?: number
  readOnly?: boolean
  schema: ReviewFieldSchema
  selectionDepth?: number
  sharedGridSelection?: GridSelection
  sharedNestedStack?: ArrayNestedView[]
  value: JsonValue
  viewSide?: ArrayReviewSide
  metadataPath?: string
  onChange?: (value: JsonValue) => void
  onGridSelectionChange?: (
    selection: GridSelection,
    side: ArrayReviewSide,
    depth: number
  ) => void
  onNestedStackChange?: (
    stack: ArrayNestedView[],
    side: ArrayReviewSide
  ) => void
  onLocationHover?: (location?: ReviewLocation) => void
  resolveArrayItemMetadataPath?: (
    metadataPath: string,
    rowIndex: number,
    rowValue: JsonValue
  ) => string | undefined
  resolveLocation?: (metadataPath: string) => ReviewLocation | undefined
}) {
  const rows = getArrayValue(value)
  const itemSchema = getArrayItemSchema(schema)
  const gridTheme = useHumanReviewGridTheme()
  const isDark = useIsDarkTheme()
  const [localNestedStack, setLocalNestedStack] = React.useState<
    ArrayNestedView[]
  >([])
  const [localGridSelection, setLocalGridSelection] =
    React.useState<GridSelection>(emptyGridSelection)
  const fastTextOverlayOpenRef = React.useRef(false)
  const nestedStack = sharedNestedStack ?? localNestedStack
  const visibleNestedStack = nestedStack.slice(nestedStackBaseDepth)
  const activeNestedView = visibleNestedStack[0] ?? null
  const activeNestedValue = activeNestedView
    ? getNestedArrayValue({
        value,
        schema,
        nestedStack: [activeNestedView],
      })
    : null
  const isActiveNestedSource =
    Boolean(activeNestedView) && activeNestedSide === viewSide
  const isMirroredNestedTarget =
    Boolean(activeNestedView) &&
    activeNestedSide !== null &&
    activeNestedSide !== viewSide
  const blueCellText = isDark ? "rgb(147, 197, 253)" : "rgb(37, 99, 235)"
  const selectedCellBackground = isDark
    ? "rgba(37, 99, 235, 0.2)"
    : "rgba(219, 234, 254, 0.85)"
  const mirroredCellBackground = isDark
    ? "rgba(167, 139, 250, 0.18)"
    : "rgba(237, 233, 254, 0.9)"
  const mirroredHighlightRegions = React.useMemo<
    React.ComponentProps<typeof DataEditor>["highlightRegions"]
  >(() => {
    if (
      !activeSelectionSide ||
      activeSelectionSide === viewSide ||
      selectionDepth !== nestedStackBaseDepth
    ) {
      return undefined
    }

    const ranges = getGridSelectionRanges(sharedGridSelection)
    if (!ranges.length) return undefined

    return ranges.map((range) => ({
      color: mirroredCellBackground,
      range,
      style: "dashed" as const,
    }))
  }, [
    activeSelectionSide,
    mirroredCellBackground,
    nestedStackBaseDepth,
    selectionDepth,
    sharedGridSelection,
    viewSide,
  ])

  const setNestedStack = React.useCallback(
    (
      updater:
        | ArrayNestedView[]
        | ((current: ArrayNestedView[]) => ArrayNestedView[])
    ) => {
      if (onNestedStackChange) {
        const nextStack =
          typeof updater === "function" ? updater(nestedStack) : updater

        onNestedStackChange(nextStack, viewSide)
        return
      }

      setLocalNestedStack(updater)
    },
    [nestedStack, onNestedStackChange, viewSide]
  )

  const handleGridSelectionChange = React.useCallback(
    (selection: GridSelection) => {
      flushSync(() => {
        setLocalGridSelection((current) =>
          areGridSelectionsEqual(current, selection) ? current : selection
        )
      })

      if (onGridSelectionChange) {
        if (
          activeSelectionSide === viewSide &&
          sharedGridSelection &&
          areGridSelectionsEqual(sharedGridSelection, selection)
        ) {
          return
        }

        React.startTransition(() => {
          onGridSelectionChange(selection, viewSide, nestedStackBaseDepth)
        })
        return
      }
    },
    [
      activeSelectionSide,
      nestedStackBaseDepth,
      onGridSelectionChange,
      sharedGridSelection,
      viewSide,
    ]
  )

  const columns = React.useMemo<GridColumn[]>(() => {
    if (itemSchema.type === "object") {
      const propertyEntries = Object.entries(itemSchema.properties ?? {})

      if (propertyEntries.length) {
        return propertyEntries.map(([key, propertySchema]) => ({
          id: key,
          title: propertySchema.title ?? key,
          width: isComplexSchema(propertySchema) ? 148 : 132,
        }))
      }
    }

    return [
      {
        id: "value",
        title: itemSchema.title ?? "Value",
        width: isComplexSchema(itemSchema) ? 148 : 180,
      },
    ]
  }, [itemSchema])

  const getCellContent = React.useCallback(
    ([col, row]: Item): GridCell => {
      const column = columns[col]
      const columnId = String(column?.id ?? "value")
      const rowValue = rows[row] ?? null
      const cellSchema = getCellSchemaForArrayColumn(itemSchema, columnId)
      const cellValue = getCellValueForArrayColumn(
        rowValue,
        itemSchema,
        columnId
      )
      const matchedNestedCell =
        activeNestedView?.rowIndex === row &&
        activeNestedView.columnId === columnId
      const nestedCellTheme: Partial<Theme> | undefined = matchedNestedCell
        ? {
            bgCell: isMirroredNestedTarget
              ? mirroredCellBackground
              : selectedCellBackground,
            textDark: blueCellText,
          }
        : undefined

      if (isComplexSchema(cellSchema)) {
        return {
          kind: GridCellKind.Text,
          data: summarizeComplexValue(cellValue),
          displayData: summarizeComplexValue(cellValue),
          allowOverlay: false,
          readonly: true,
          cursor: "pointer",
          activationBehaviorOverride: "double-click",
          themeOverride: {
            textDark: blueCellText,
            ...(nestedCellTheme ?? {}),
          },
        }
      }

      if (cellSchema.type === "boolean") {
        return {
          kind: GridCellKind.Boolean,
          data: typeof cellValue === "boolean" ? cellValue : false,
          allowOverlay: false,
          readonly: readOnly,
        }
      }

      if (cellSchema.type === "number" || cellSchema.type === "integer") {
        return {
          kind: GridCellKind.Number,
          data: typeof cellValue === "number" ? cellValue : undefined,
          displayData: typeof cellValue === "number" ? String(cellValue) : "",
          allowOverlay: true,
          readonly: false,
        }
      }

      return {
        kind: GridCellKind.Text,
        data:
          cellValue === null ||
          isJsonObject(cellValue) ||
          isJsonArray(cellValue)
            ? ""
            : String(cellValue),
        displayData:
          cellValue === null ||
          isJsonObject(cellValue) ||
          isJsonArray(cellValue)
            ? ""
            : String(cellValue),
        allowOverlay: true,
        readonly: false,
      }
    },
    [
      activeNestedView,
      blueCellText,
      columns,
      isMirroredNestedTarget,
      itemSchema,
      mirroredCellBackground,
      readOnly,
      rows,
      selectedCellBackground,
    ]
  )

  const updateCellValue = React.useCallback(
    ([col, row]: Item, nextCell: EditableGridCell) => {
      if (readOnly || !onChange) return

      const column = columns[col]
      const columnId = String(column?.id ?? "value")
      const rowValue = rows[row] ?? null
      const cellSchema = getCellSchemaForArrayColumn(itemSchema, columnId)
      const nextValue = applyPrimitiveEdit(cellSchema, nextCell)

      if (nextValue === undefined) return

      const nextRowValue =
        itemSchema.type === "object"
          ? setObjectValue(rowValue, columnId, nextValue)
          : nextValue

      onChange(setArrayItemValue(value, row, nextRowValue))
    },
    [columns, itemSchema, onChange, readOnly, rows, value]
  )
  const provideEditor = React.useCallback<
    NonNullable<React.ComponentProps<typeof DataEditor>["provideEditor"]>
  >(
    (cell) => {
      if (
        cell.kind !== GridCellKind.Text &&
        cell.kind !== GridCellKind.Number
      ) {
        return undefined
      }

      return {
        editor: (props) => {
          if (
            props.value.kind !== GridCellKind.Text &&
            props.value.kind !== GridCellKind.Number
          ) {
            return null
          }

          return (
            <HumanReviewTextOverlayEditor
              {...(props as React.ComponentProps<
                ProvideEditorComponent<HumanReviewOverlayCell>
              >)}
              overlayOpenRef={fastTextOverlayOpenRef}
              readOnly={readOnly}
            />
          )
        },
      }
    },
    [readOnly]
  )
  const handleOutsideClick = React.useCallback(
    () => !fastTextOverlayOpenRef.current,
    []
  )
  const handleItemHovered = React.useCallback(
    (args: GridMouseEventArgs) => {
      if (!onLocationHover || !resolveLocation || !metadataPath) return

      if (args.kind !== "cell") {
        onLocationHover(undefined)
        return
      }

      const [col, row] = args.location
      const column = columns[col]
      const rowValue = rows[row]
      if (!column || rowValue === undefined) {
        onLocationHover(undefined)
        return
      }

      const columnId = String(column.id ?? "value")
      const rowMetadataPath = resolveArrayItemMetadataPath
        ? resolveArrayItemMetadataPath(metadataPath, row, rowValue)
        : `${metadataPath}[${row}]`
      if (!rowMetadataPath) {
        onLocationHover(undefined)
        return
      }

      const propertyMetadataPath =
        itemSchema.type === "object"
          ? `${rowMetadataPath}.${columnId}`
          : rowMetadataPath

      onLocationHover(
        resolveLocation(propertyMetadataPath) ??
          resolveLocation(rowMetadataPath)
      )
    },
    [
      columns,
      itemSchema.type,
      metadataPath,
      onLocationHover,
      resolveArrayItemMetadataPath,
      resolveLocation,
      rows,
    ]
  )

  const openNestedCell = React.useCallback(
    ([col, row]: Item) => {
      const column = columns[col]
      const columnId = String(column?.id ?? "value")
      const rowValue = rows[row] ?? null
      const cellSchema = getCellSchemaForArrayColumn(itemSchema, columnId)

      if (!isComplexSchema(cellSchema)) return

      setNestedStack((current) => {
        const nextView = {
          rowIndex: row,
          columnId,
          title: `${column?.title ?? columnId} / row ${row + 1}`,
          schema: cellSchema,
          value: getCellValueForArrayColumn(rowValue, itemSchema, columnId),
        }
        const currentView = current[nestedStackBaseDepth]

        if (
          currentView?.rowIndex === nextView.rowIndex &&
          currentView.columnId === nextView.columnId
        ) {
          return current
        }

        return [...current.slice(0, nestedStackBaseDepth), nextView]
      })
    },
    [columns, itemSchema, nestedStackBaseDepth, rows, setNestedStack]
  )

  const updateNestedValue = React.useCallback(
    (nextNestedValue: JsonValue) => {
      if (!activeNestedView || readOnly || !onChange) return

      onChange(
        setNestedArrayValue({
          value,
          schema,
          nestedStack: visibleNestedStack.slice(0, 1),
          nextNestedValue,
        })
      )
      setNestedStack((current) =>
        current.map((view, index) =>
          index === nestedStackBaseDepth
            ? { ...view, value: nextNestedValue }
            : view
        )
      )
    },
    [
      activeNestedView,
      nestedStackBaseDepth,
      onChange,
      readOnly,
      schema,
      setNestedStack,
      value,
      visibleNestedStack,
    ]
  )

  return (
    <div
      onMouseLeave={() => onLocationHover?.(undefined)}
      className={cn(
        "relative overflow-hidden rounded-md border bg-background transition-[border-color,background-color,box-shadow] focus-within:border-blue-500/50 focus-within:shadow-[0_0_0_1px_rgb(59_130_246_/_8%)] hover:border-blue-500/50",
        isActiveNestedSource &&
          "border-blue-500/60 bg-blue-500/5 shadow-[0_0_0_1px_rgb(59_130_246_/_10%)]"
      )}
    >
      <div>
        <div className="flex h-8 items-center justify-between gap-2 border-b px-2 text-[11px] font-medium text-muted-foreground">
          <span>{label}</span>
          <span>{rows.length} rows</span>
        </div>
        <div className="h-[220px]">
          <DataEditor
            columns={columns}
            rows={rows.length}
            getCellContent={getCellContent}
            cellActivationBehavior="double-click"
            gridSelection={localGridSelection}
            highlightRegions={mirroredHighlightRegions}
            onCellEdited={updateCellValue}
            onCellActivated={openNestedCell}
            onGridSelectionChange={handleGridSelectionChange}
            onItemHovered={handleItemHovered}
            provideEditor={provideEditor}
            isOutsideClick={handleOutsideClick}
            rowMarkers="number"
            smoothScrollX
            smoothScrollY
            theme={gridTheme}
            width="100%"
            height="100%"
            rowHeight={32}
            headerHeight={34}
          />
        </div>
      </div>
      {activeNestedView ? (
        <div className="absolute inset-0 z-10 flex flex-col bg-background">
          <div className="flex h-8 items-center gap-2 border-b px-2">
            <Button
              type="button"
              variant="ghost"
              size="icon-sm"
              className="size-6 text-muted-foreground"
              onClick={() =>
                setNestedStack((current) =>
                  current.slice(
                    0,
                    Math.max(nestedStackBaseDepth, current.length - 1)
                  )
                )
              }
              aria-label="Back to parent array"
            >
              <IconPlaceholder
                lucide="ChevronLeft"
                tabler="IconChevronLeft"
                hugeicons="ArrowLeft01Icon"
                phosphor="CaretLeftIcon"
                remixicon="RiArrowLeftSLine"
                className="size-3.5"
              />
            </Button>
            <div className="min-w-0 flex-1 truncate text-xs font-medium text-foreground">
              {activeNestedView.title}
            </div>
          </div>
          <div className="min-h-0 flex-1 overflow-auto p-2">
            {activeNestedView.schema.type === "array" ? (
              <HumanReviewArrayValueGrid
                label={
                  activeNestedView.schema.title ?? activeNestedView.columnId
                }
                nestedStackBaseDepth={nestedStackBaseDepth + 1}
                readOnly={readOnly}
                schema={activeNestedView.schema}
                sharedGridSelection={sharedGridSelection}
                sharedNestedStack={sharedNestedStack}
                value={activeNestedValue}
                viewSide={viewSide}
                activeNestedSide={activeNestedSide}
                activeSelectionSide={activeSelectionSide}
                selectionDepth={selectionDepth}
                onChange={updateNestedValue}
                onGridSelectionChange={onGridSelectionChange}
                onNestedStackChange={onNestedStackChange}
                resolveArrayItemMetadataPath={resolveArrayItemMetadataPath}
                resolveLocation={resolveLocation}
              />
            ) : (
              <HumanReviewObjectValueEditor
                schema={activeNestedView.schema}
                value={activeNestedValue}
                originalValue={activeNestedValue}
                readOnly={readOnly}
                onChange={updateNestedValue}
              />
            )}
          </div>
        </div>
      ) : null}
    </div>
  )
}

function HumanReviewObjectValueEditor({
  schema,
  value,
  originalValue,
  readOnly = false,
  onChange,
}: {
  schema: ReviewFieldSchema
  value: JsonValue
  originalValue: JsonValue
  readOnly?: boolean
  onChange: (value: JsonValue) => void
}) {
  const propertyEntries = Object.entries(schema.properties ?? {})

  if (!propertyEntries.length) {
    return (
      <div className="rounded-md bg-background px-2 py-1.5 text-sm text-muted-foreground">
        No properties
      </div>
    )
  }

  return (
    <div className="space-y-2">
      {propertyEntries.map(([propertyKey, propertySchema]) => (
        <HumanReviewFieldCard
          key={propertyKey}
          field={{
            key: propertyKey,
            schema: propertySchema,
            actual: getObjectValue(originalValue, propertyKey),
            expected: getObjectValue(originalValue, propertyKey),
          }}
          value={getObjectValue(value, propertyKey)}
          originalValue={getObjectValue(originalValue, propertyKey)}
          readOnly={readOnly}
          onChange={(childValue) =>
            !readOnly &&
            onChange(setObjectValue(value, propertyKey, childValue))
          }
          onUndo={() =>
            !readOnly &&
            onChange(
              setObjectValue(
                value,
                propertyKey,
                getObjectValue(originalValue, propertyKey)
              )
            )
          }
          onSetNull={() =>
            !readOnly && onChange(setObjectValue(value, propertyKey, null))
          }
        />
      ))}
    </div>
  )
}

type HumanReviewFieldCardProps = {
  field: ReviewField
  value: JsonValue
  originalValue: JsonValue
  active?: boolean
  activeFieldKey?: string
  readOnly?: boolean
  showExpected?: boolean
  onChange: (value: JsonValue) => void
  onFieldFocus?: (field: ReviewField) => void
  onLocationHover?: (location?: ReviewLocation) => void
  onUndo: () => void
  onSetNull: () => void
  resolveArrayItemMetadataPath?: (
    metadataPath: string,
    rowIndex: number,
    rowValue: JsonValue
  ) => string | undefined
  resolveLocation?: (metadataPath: string) => ReviewLocation | undefined
}

function areHumanReviewFieldCardPropsEqual(
  previous: HumanReviewFieldCardProps,
  next: HumanReviewFieldCardProps
) {
  return (
    previous.field === next.field &&
    Object.is(previous.value, next.value) &&
    Object.is(previous.originalValue, next.originalValue) &&
    previous.active === next.active &&
    previous.activeFieldKey === next.activeFieldKey &&
    previous.readOnly === next.readOnly &&
    previous.showExpected === next.showExpected &&
    previous.onFieldFocus === next.onFieldFocus &&
    previous.onLocationHover === next.onLocationHover &&
    previous.resolveArrayItemMetadataPath ===
      next.resolveArrayItemMetadataPath &&
    previous.resolveLocation === next.resolveLocation
  )
}

const HumanReviewFieldCard = React.memo(
  HumanReviewFieldCardBase,
  areHumanReviewFieldCardPropsEqual
)

function HumanReviewFieldCardBase({
  field,
  value,
  originalValue,
  active,
  activeFieldKey,
  readOnly = false,
  showExpected = true,
  onChange,
  onFieldFocus,
  onLocationHover,
  onUndo,
  onSetNull,
  resolveArrayItemMetadataPath,
  resolveLocation,
}: HumanReviewFieldCardProps) {
  const isExpectedEditable = showExpected && !readOnly
  const modified = showExpected && !jsonValuesEqual(value, originalValue)
  const fieldTypeIcon = React.createElement(getFieldIcon(field.schema.type), {
    className: "size-3.5",
  })
  const propertyEntries = Object.entries(field.schema.properties ?? {})
  const [syncedArrayNestedView, setSyncedArrayNestedView] =
    React.useState<SyncedArrayNestedView>(EMPTY_SYNCED_ARRAY_NESTED_VIEW)
  const [syncedArraySelection, setSyncedArraySelection] =
    React.useState<SyncedArraySelection>(EMPTY_SYNCED_ARRAY_SELECTION)
  const updateSyncedArrayNestedView = React.useCallback(
    (stack: ArrayNestedView[], side: ArrayReviewSide) => {
      const activeSide = stack.length ? side : null

      setSyncedArrayNestedView((current) =>
        current.activeSide === activeSide &&
        areArrayNestedViewsEqual(current.stack, stack)
          ? current
          : {
              activeSide,
              stack,
            }
      )
    },
    []
  )
  const updateSyncedArraySelection = React.useCallback(
    (gridSelection: GridSelection, side: ArrayReviewSide, depth: number) => {
      const activeSide = gridSelection.current ? side : null

      setSyncedArraySelection((current) =>
        current.activeSide === activeSide &&
        current.depth === depth &&
        areGridSelectionsEqual(current.gridSelection, gridSelection)
          ? current
          : {
              activeSide,
              depth,
              gridSelection,
            }
      )
    },
    []
  )
  const focusAndHoverField = React.useCallback(() => {
    onFieldFocus?.(field)
    onLocationHover?.(getReviewFieldLocation(field, resolveLocation))
  }, [field, onFieldFocus, onLocationHover, resolveLocation])

  return (
    <div
      tabIndex={0}
      onFocusCapture={focusAndHoverField}
      onMouseEnter={focusAndHoverField}
      onMouseLeave={() => onLocationHover?.(undefined)}
      className={cn(
        "rounded-lg border bg-background p-3 transition-[border-color,background-color,box-shadow] focus-within:border-blue-500/50 focus-within:bg-blue-500/5 hover:border-blue-500/50 hover:bg-blue-500/5 focus-visible:ring-2 focus-visible:ring-blue-500/20 focus-visible:outline-none",
        active &&
          "border-blue-500/60 bg-blue-500/5 shadow-[0_0_0_1px_rgb(59_130_246_/_8%)]"
      )}
    >
      <div className="mb-3 flex min-h-8 items-start justify-between gap-3">
        <div className="min-w-0">
          <div className="flex min-w-0 items-center gap-2">
            <div className="min-w-0">
              <div className="truncate text-sm font-medium">
                {field.schema.title ?? field.key}
              </div>
            </div>
            <span
              className={cn(
                "size-2 shrink-0 rounded-full bg-amber-400",
                !modified && "opacity-0"
              )}
            />
          </div>
          <div className="truncate text-xs text-muted-foreground">
            {field.key}
          </div>
        </div>
        <div className="flex shrink-0 items-center gap-1">
          {isExpectedEditable && modified ? (
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon-sm"
                  className="text-muted-foreground"
                  onClick={onUndo}
                  aria-label={`Undo ${field.key}`}
                >
                  <IconPlaceholder
                    lucide="Undo2"
                    tabler="IconArrowBackUp"
                    hugeicons="Undo02Icon"
                    phosphor="ArrowUUpLeftIcon"
                    remixicon="RiArrowGoBackLine"
                    className="size-4"
                  />
                </Button>
              </TooltipTrigger>
              <TooltipContent>Revert changes</TooltipContent>
            </Tooltip>
          ) : null}
          {isExpectedEditable ? (
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon-sm"
                  className="text-muted-foreground"
                  onClick={onSetNull}
                  aria-label={`Set ${field.key} to null`}
                >
                  <IconPlaceholder
                    lucide="CircleX"
                    tabler="IconCircleX"
                    hugeicons="CancelCircleIcon"
                    phosphor="XCircleIcon"
                    remixicon="RiCloseCircleLine"
                    className="size-4"
                  />
                </Button>
              </TooltipTrigger>
              <TooltipContent>Set to NULL</TooltipContent>
            </Tooltip>
          ) : null}
          <div className="flex h-6 items-center gap-1 rounded-md border bg-muted/50 px-1.5 text-xs text-muted-foreground">
            {fieldTypeIcon}
            {field.schema.type}
          </div>
        </div>
      </div>
      {field.schema.type === "object" ? (
        <div className="rounded-md border bg-muted/25 p-2">
          <div className="mb-2 flex items-center justify-between gap-3 text-[11px] font-medium text-muted-foreground">
            <span>Properties</span>
            <span>{propertyEntries.length} fields</span>
          </div>
          <div className="space-y-2">
            {propertyEntries.length ? (
              propertyEntries.map(([propertyKey, schema]) => {
                const childField: ReviewField = {
                  key: `${field.key}.${propertyKey}`,
                  schema,
                  actual: getObjectValue(field.actual, propertyKey),
                  expected: getObjectValue(originalValue, propertyKey),
                  metadataPath: `${field.metadataPath ?? field.key}.${propertyKey}`,
                }

                return (
                  <HumanReviewFieldCard
                    key={childField.key}
                    field={childField}
                    value={getObjectValue(value, propertyKey)}
                    originalValue={childField.expected}
                    active={childField.key === activeFieldKey}
                    activeFieldKey={activeFieldKey}
                    readOnly={readOnly}
                    showExpected={showExpected}
                    onChange={(childValue) =>
                      onChange(setObjectValue(value, propertyKey, childValue))
                    }
                    onFieldFocus={onFieldFocus}
                    onLocationHover={onLocationHover}
                    onUndo={() =>
                      onChange(
                        setObjectValue(value, propertyKey, childField.expected)
                      )
                    }
                    onSetNull={() =>
                      onChange(setObjectValue(value, propertyKey, null))
                    }
                    resolveArrayItemMetadataPath={resolveArrayItemMetadataPath}
                    resolveLocation={resolveLocation}
                  />
                )
              })
            ) : (
              <div className="rounded-md bg-background px-2 py-1.5 text-sm text-muted-foreground">
                No properties
              </div>
            )}
          </div>
        </div>
      ) : field.schema.type === "array" ? (
        <div className="grid gap-2">
          <HumanReviewArrayValueGrid
            activeNestedSide={syncedArrayNestedView.activeSide}
            activeSelectionSide={syncedArraySelection.activeSide}
            label="Actual"
            metadataPath={field.metadataPath ?? field.key}
            readOnly
            resolveArrayItemMetadataPath={resolveArrayItemMetadataPath}
            resolveLocation={resolveLocation}
            schema={field.schema}
            selectionDepth={syncedArraySelection.depth}
            sharedGridSelection={syncedArraySelection.gridSelection}
            sharedNestedStack={syncedArrayNestedView.stack}
            value={field.actual}
            viewSide="actual"
            onGridSelectionChange={updateSyncedArraySelection}
            onLocationHover={onLocationHover}
            onNestedStackChange={updateSyncedArrayNestedView}
          />
          {showExpected ? (
            <HumanReviewArrayValueGrid
              activeNestedSide={syncedArrayNestedView.activeSide}
              activeSelectionSide={syncedArraySelection.activeSide}
              label="Expected"
              metadataPath={field.metadataPath ?? field.key}
              readOnly={readOnly}
              resolveArrayItemMetadataPath={resolveArrayItemMetadataPath}
              resolveLocation={resolveLocation}
              schema={field.schema}
              selectionDepth={syncedArraySelection.depth}
              sharedGridSelection={syncedArraySelection.gridSelection}
              sharedNestedStack={syncedArrayNestedView.stack}
              value={value}
              viewSide="expected"
              onChange={onChange}
              onGridSelectionChange={updateSyncedArraySelection}
              onLocationHover={onLocationHover}
              onNestedStackChange={updateSyncedArrayNestedView}
            />
          ) : null}
        </div>
      ) : (
        <div className={cn("grid gap-2", showExpected && "sm:grid-cols-2")}>
          <div className="rounded-md border bg-muted/30 p-2">
            {field.schema.description ? (
              <p className="mb-2 text-xs text-muted-foreground">
                {field.schema.description}
              </p>
            ) : null}
            <div className="mb-1 text-[11px] font-medium text-muted-foreground">
              Actual
            </div>
            <div className="min-h-7 rounded-md bg-background px-2 py-1.5 text-sm">
              {formatValue(field.actual)}
            </div>
          </div>
          {showExpected ? (
            <div className="rounded-md border bg-muted/30 p-2">
              <div className="mb-1 text-[11px] font-medium text-muted-foreground">
                Expected
              </div>
              <HumanReviewValueInput
                readOnly={readOnly}
                schema={field.schema}
                value={getPrimitiveValue(value)}
                onChange={onChange}
              />
            </div>
          ) : null}
        </div>
      )}
    </div>
  )
}

export function HumanReviewHighlight({
  location,
}: {
  location: ReviewLocation
}) {
  const area = location.area

  return (
    <div
      className={cn(
        "pointer-events-none absolute z-10 border",
        REVIEW_HIGHLIGHT_STYLE
      )}
      style={{
        left: `${area.left}%`,
        top: `${area.top}%`,
        width: `${area.width}%`,
        height: `${area.height}%`,
      }}
    />
  )
}

export function JsonDiffView({
  actual,
  expected,
  theme = "light",
}: {
  actual: JsonObject
  expected: JsonObject
  theme?: HumanReviewTheme
}) {
  const oldFile = React.useMemo(
    () => ({
      name: "actual.json",
      contents: formatJson(actual),
      lang: "json",
    }),
    [actual]
  )
  const newFile = React.useMemo(
    () => ({
      name: "expected.json",
      contents: formatJson(expected),
      lang: "json",
    }),
    [expected]
  )

  return (
    <ScrollAreaVirtualizer
      className="h-full bg-surface/60"
      contentClassName="min-w-full"
    >
      <div className="bounding-box-citations-diff h-full text-xs">
        <MultiFileDiff
          className="block min-w-full"
          oldFile={oldFile}
          newFile={newFile}
          options={{
            diffStyle: "split",
            disableFileHeader: true,
            diffIndicators: "bars",
            hunkSeparators: "line-info-basic",
            overflow: "wrap",
            themeType: theme,
            theme: {
              light: "pierre-light-soft",
              dark: "pierre-dark-soft",
            },
          }}
        />
      </div>
    </ScrollAreaVirtualizer>
  )
}

export function JsonCodeView({
  name = "actual.json",
  theme = "light",
  value,
}: {
  name?: string
  theme?: HumanReviewTheme
  value: JsonValue
}) {
  const file = React.useMemo(() => {
    const contents = formatJson(value)

    return {
      name,
      contents,
      lang: "json" as const,
      cacheKey: contents,
    }
  }, [name, value])

  return (
    <div
      data-rehype-pretty-code-figure
      className="relative m-0! h-full overflow-hidden rounded-none! bg-code text-code-foreground"
    >
      <WorkerPoolContextProvider
        poolOptions={CODE_WORKER_POOL_OPTIONS}
        highlighterOptions={CODE_HIGHLIGHTER_OPTIONS}
      >
        <ScrollAreaVirtualizer
          key={`${file.cacheKey}:${theme}`}
          className="h-full min-w-0"
          contentClassName="min-w-full"
        >
          <File
            key={`${file.cacheKey}:${theme}`}
            className="block min-w-full"
            file={file}
            metrics={CODE_VIRTUAL_FILE_METRICS}
            style={CODE_FILE_THEME}
            options={{
              disableFileHeader: true,
              overflow: "scroll",
              themeType: theme,
              theme: {
                light: "pierre-light-soft",
                dark: "pierre-dark-soft",
              },
            }}
          />
        </ScrollAreaVirtualizer>
      </WorkerPoolContextProvider>
    </div>
  )
}

export function HumanReviewPanel({
  fields = REVIEW_FIELDS,
  activeFieldKey,
  className,
  onFieldFocus,
  onLocationHover,
  resolveArrayItemMetadataPath,
  resolveLocation,
  showExpected = true,
  theme = "light",
}: {
  fields?: ReviewField[]
  activeFieldKey?: string
  className?: string
  onFieldFocus?: (field: ReviewField) => void
  onLocationHover?: (location?: ReviewLocation) => void
  resolveArrayItemMetadataPath?: (
    metadataPath: string,
    rowIndex: number,
    rowValue: JsonValue
  ) => string | undefined
  resolveLocation?: (metadataPath: string) => ReviewLocation | undefined
  showExpected?: boolean
  theme?: HumanReviewTheme
} = {}) {
  const [activeTab, setActiveTab] = React.useState("form")
  const actualValues = React.useMemo(
    () => valuesFromFields(fields, "actual"),
    [fields]
  )
  const initialExpectedValues = React.useMemo(
    () => valuesFromFields(fields, "expected"),
    [fields]
  )
  const [expected, setExpected] = React.useState<JsonObject>(
    initialExpectedValues
  )

  const [previousExpectedValues, setPreviousExpectedValues] = React.useState(
    initialExpectedValues
  )
  if (!Object.is(previousExpectedValues, initialExpectedValues)) {
    setPreviousExpectedValues(initialExpectedValues)
    setExpected(initialExpectedValues)
  }

  const updateValue = React.useCallback((key: string, value: JsonValue) => {
    setExpected((current) =>
      Object.is(current[key], value) ? current : { ...current, [key]: value }
    )
  }, [])

  return (
    <TooltipProvider delay={200}>
      <Tabs
        value={activeTab}
        onValueChange={setActiveTab}
        className={cn("flex h-[560px] flex-col gap-0 bg-background", className)}
      >
        <div className="flex min-h-12 items-center justify-between gap-3 border-b px-3">
          <TabsList className="h-8 sm:h-7">
            <TabsTrigger value="form" className="h-7 sm:h-6">
              <IconPlaceholder
                lucide="CaptionsIcon"
                tabler="IconTextCaption"
                hugeicons="TextCheckIcon"
                phosphor="TextTIcon"
                remixicon="RiTextWrap"
                className="size-4"
              />
              Form
            </TabsTrigger>
            <TabsTrigger value="json" className="h-7 sm:h-6">
              <IconPlaceholder
                lucide="SquareCode"
                tabler="IconSourceCode"
                hugeicons="SourceCodeSquareIcon"
                phosphor="CodeIcon"
                remixicon="RiCodeBlock"
                className="size-4"
              />
              JSON
            </TabsTrigger>
          </TabsList>
        </div>
        <TabsContent value="form" keepMounted className="min-h-0 flex-1">
          <ScrollArea className="h-full" scrollFade>
            <div className="space-y-3 p-3">
              {fields.map((field) => (
                <HumanReviewFieldCard
                  key={field.key}
                  field={field}
                  value={
                    showExpected ? (expected[field.key] ?? null) : field.actual
                  }
                  originalValue={showExpected ? field.expected : field.actual}
                  active={
                    field.key === activeFieldKey ||
                    activeFieldKey?.startsWith(`${field.key}.`)
                  }
                  activeFieldKey={activeFieldKey}
                  showExpected={showExpected}
                  onChange={(value) => updateValue(field.key, value)}
                  onFieldFocus={onFieldFocus}
                  onLocationHover={onLocationHover}
                  onUndo={() => updateValue(field.key, field.expected)}
                  onSetNull={() => updateValue(field.key, null)}
                  resolveArrayItemMetadataPath={resolveArrayItemMetadataPath}
                  resolveLocation={resolveLocation}
                />
              ))}
            </div>
          </ScrollArea>
        </TabsContent>
        <TabsContent value="json" keepMounted className="min-h-0 flex-1">
          {showExpected ? (
            <JsonDiffView
              actual={actualValues}
              expected={expected}
              theme={theme}
            />
          ) : (
            <JsonCodeView value={actualValues} theme={theme} />
          )}
        </TabsContent>
      </Tabs>
    </TooltipProvider>
  )
}
author/apps/v4/lib/registry-icon-props.ts
파일 저장

import type * as React from "react"

export type RegistryIconProps = Omit<
  React.ComponentProps<"svg">,
  "children" | "strokeWidth"
> & { strokeWidth?: number }
author/apps/v4/lib/utils.ts
파일 저장

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

import { siteConfig } from "@/lib/config"

export function cn(...inputs: ClassValue[]): string {
  return twMerge(clsx(inputs))
}

export function absoluteUrl(path: string): string {
  const normalizedPath = path.startsWith("/") ? path : `/${path}`

  return `${siteConfig.url}${normalizedPath === "/" ? "" : normalizedPath}`
}
author/apps/v4/components/ui/button.tsx
파일 저장

"use client"

import * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"
import { Spinner } from "@/components/ui/spinner"

export const buttonVariants = cva(
  "relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg border text-base font-medium whitespace-nowrap transition-shadow outline-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-loading:text-transparent data-loading:select-none sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4",
  {
    defaultVariants: {
      size: "default",
      variant: "default",
    },
    variants: {
      size: {
        default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8",
        icon: "size-9 sm:size-8",
        "icon-lg": "size-10 sm:size-9",
        "icon-sm": "size-8 sm:size-7",
        "icon-xl":
          "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
        "icon-xs":
          "size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
        lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
        sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:h-7",
        xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
        xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5",
      },
      variant: {
        default:
          "border-primary bg-primary text-primary-foreground shadow-xs shadow-primary/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-primary/90 data-pressed:bg-primary/90 *:data-[slot=button-loading-indicator]:text-primary-foreground [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none",
        destructive:
          "border-destructive bg-destructive text-white shadow-xs shadow-destructive/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-destructive/90 data-pressed:bg-destructive/90 *:data-[slot=button-loading-indicator]:text-white [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none",
        "destructive-outline":
          "border-input bg-popover text-destructive-foreground shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:border-destructive/32 hover:bg-destructive/4 data-pressed:border-destructive/32 data-pressed:bg-destructive/4 *:data-[slot=button-loading-indicator]:text-foreground dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none",
        ghost:
          "border-transparent text-foreground hover:bg-accent data-pressed:bg-accent *:data-[slot=button-loading-indicator]:text-foreground",
        link: "border-transparent text-foreground underline-offset-4 hover:underline data-pressed:underline *:data-[slot=button-loading-indicator]:text-foreground",
        outline:
          "border-input bg-popover text-foreground shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-accent/50 data-pressed:bg-accent/50 *:data-[slot=button-loading-indicator]:text-foreground dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:hover:bg-input/64 dark:data-pressed:bg-input/64 [:disabled,:active,[data-pressed]]:shadow-none",
        secondary:
          "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90 *:data-[slot=button-loading-indicator]:text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80",
      },
    },
  }
)

export interface ButtonProps extends useRender.ComponentProps<"button"> {
  variant?: VariantProps<typeof buttonVariants>["variant"]
  size?: VariantProps<typeof buttonVariants>["size"]
  loading?: boolean
}

export function Button({
  className,
  variant,
  size,
  render,
  children,
  loading = false,
  disabled: disabledProp,
  ...props
}: ButtonProps): React.ReactElement {
  const isDisabled: boolean = Boolean(loading || disabledProp)
  const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] =
    render ? undefined : "button"

  const defaultProps = {
    children: (
      <>
        {children}
        {loading && (
          <Spinner
            className="pointer-events-none absolute"
            data-slot="button-loading-indicator"
          />
        )}
      </>
    ),
    className: cn(buttonVariants({ className, size, variant })),
    "aria-disabled": loading || undefined,
    "data-loading": loading ? "" : undefined,
    "data-slot": "button",
    disabled: isDisabled,
    type: typeValue,
  }

  return useRender({
    defaultTagName: "button",
    props: mergeProps<"button">(defaultProps, props),
    render,
  })
}
author/apps/v4/components/ui/input.tsx
파일 저장

"use client"

export {
  Input,
  InputPrimitive,
  type InputProps,
} from "@/registry/new-york-v4/ui/input"
author/apps/v4/components/ui/scroll-area.tsx
파일 저장

"use client"

import * as React from "react"
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"

import { cn } from "@/lib/utils"

export function ScrollArea({
  className,
  children,
  orientation = "both",
  scrollFade = false,
  scrollbarGutter = false,
  scrollbarOverflowOnly = false,
  viewportClassName,
  viewportProps,
  viewportRef,
  ...props
}: ScrollAreaPrimitive.Root.Props & {
  orientation?: "vertical" | "horizontal" | "both"
  scrollFade?: boolean
  scrollbarGutter?: boolean
  scrollbarOverflowOnly?: boolean
  viewportClassName?: string
  viewportProps?: ScrollAreaPrimitive.Viewport.Props
  viewportRef?: React.Ref<HTMLDivElement>
}): React.ReactElement {
  const {
    className: viewportPropsClassName,
    key: viewportKey,
    ref: viewportPropsRef,
    ...resolvedViewportProps
  } = viewportProps ?? {}
  const composedViewportRef = React.useMemo(
    () => composeRefs(viewportPropsRef, viewportRef),
    [viewportPropsRef, viewportRef]
  )

  return (
    <ScrollAreaPrimitive.Root
      className={cn(
        "size-full min-h-0",
        scrollbarOverflowOnly && "scrollbar-overflow-only",
        className
      )}
      {...props}
    >
      <ScrollAreaPrimitive.Viewport
        key={viewportKey}
        {...resolvedViewportProps}
        ref={composedViewportRef}
        className={cn(
          "transition-shadows h-full rounded-[inherit] outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain data-has-overflow-y:overscroll-y-contain",
          scrollFade &&
            "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] [--fade-size:1.5rem]",
          scrollbarGutter && orientation !== "vertical" && "pb-3.5",
          scrollbarGutter && orientation !== "horizontal" && "pe-3.5",
          viewportPropsClassName,
          viewportClassName
        )}
        data-slot="scroll-area-viewport"
      >
        {children}
      </ScrollAreaPrimitive.Viewport>
      {orientation !== "horizontal" ? (
        <ScrollBar orientation="vertical" />
      ) : null}
      {orientation !== "vertical" ? (
        <ScrollBar orientation="horizontal" />
      ) : null}
      {orientation === "both" ? (
        <ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
      ) : null}
    </ScrollAreaPrimitive.Root>
  )
}

function composeRefs<T>(
  ...refs: Array<React.Ref<T> | undefined>
): React.RefCallback<T> {
  return (node) => {
    refs.forEach((ref) => {
      if (!ref) return
      if (typeof ref === "function") {
        ref(node)
        return
      }
      ref.current = node
    })
  }
}

export function ScrollBar({
  className,
  orientation = "vertical",
  ...props
}: ScrollAreaPrimitive.Scrollbar.Props): React.ReactElement {
  return (
    <ScrollAreaPrimitive.Scrollbar
      className={cn(
        "m-1 flex opacity-0 transition-opacity delay-300 data-hovering:opacity-100 data-hovering:delay-0 data-hovering:duration-100 data-scrolling:opacity-100 data-scrolling:delay-0 data-scrolling:duration-100 data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:w-1.5",
        className
      )}
      data-slot="scroll-area-scrollbar"
      orientation={orientation}
      {...props}
    >
      <ScrollAreaPrimitive.Thumb
        className="relative flex-1 rounded-full bg-foreground/20"
        data-slot="scroll-area-thumb"
      />
    </ScrollAreaPrimitive.Scrollbar>
  )
}

export { ScrollAreaPrimitive }
author/apps/v4/components/ui/tabs.tsx
파일 저장

"use client"

export {
  Tabs,
  TabsContent,
  TabsList,
  TabsPrimitive,
  TabsTrigger,
  type TabsVariant,
} from "@/registry/new-york-v4/ui/tabs"
author/apps/v4/components/ui/tooltip.tsx
파일 저장

"use client"

export {
  Tooltip,
  TooltipContent,
  TooltipCreateHandle,
  TooltipPopup,
  TooltipPrimitive,
  TooltipProvider,
  TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
author/apps/v4/components/icon-placeholder.tsx
파일 저장

import type React from "react"
import {
  Add01Icon,
  Alert01Icon,
  AlignBoxBottomCenterIcon,
  AlignBoxMiddleCenterIcon,
  AlignBoxTopCenterIcon,
  ArrowDown01Icon,
  ArrowDown02Icon,
  ArrowExpandDiagonal01Icon,
  ArrowExpandDiagonal02Icon,
  ArrowLeft01Icon,
  ArrowLeftRightIcon,
  ArrowRight01Icon,
  ArrowUp01Icon,
  ArrowUp02Icon,
  ArrowUpDownIcon,
  ArrowUpRight01Icon,
  Attachment01Icon,
  Book01Icon,
  BorderAll01Icon,
  BorderBottom01Icon,
  BorderHorizontalIcon,
  BorderInnerIcon,
  BorderLeft01Icon,
  BorderNone01Icon,
  BorderRight01Icon,
  BorderTop01Icon,
  BorderVerticalIcon,
  BrushIcon,
  BubbleChatIcon,
  Calendar03Icon,
  Camera01Icon,
  Cancel01Icon,
  CancelCircleIcon,
  CellsIcon,
  CheckIcon,
  CheckmarkCircle01Icon,
  CheckmarkSquare01Icon,
  ChevronDown,
  ChevronDownIcon,
  ChevronUp,
  CircleIcon,
  ClipboardIcon,
  ColorPickerIcon,
  ColumnsThreeCogIcon,
  Comment01Icon,
  CommentAdd01Icon,
  Copy01Icon,
  Cursor01Icon,
  CursorRectangleSelection01Icon,
  CursorTextIcon,
  Delete02Icon,
  DollarCircleIcon,
  Download01Icon,
  DragDropVerticalIcon,
  DropletOffIcon,
  EditOffIcon,
  ExternalLinkIcon,
  File01Icon,
  FileAddIcon,
  FileDiffIcon,
  FileImageIcon,
  FilePenIcon,
  FileSpreadsheetIcon,
  FileUploadIcon,
  FilterIcon,
  Flag01Icon,
  FloppyDiskIcon,
  FullScreenIcon,
  GalleryThumbnailsIcon,
  GitMergeIcon,
  Grid2X2XIcon,
  GridViewIcon,
  GroupItemsIcon,
  HandIcon,
  Heading01Icon,
  HighlighterIcon,
  Image01Icon,
  ImageAdd01Icon,
  ImageCompositionIcon,
  InformationCircleIcon,
  InputNumericIcon,
  InputTextIcon,
  Key01Icon,
  KeyboardIcon,
  Layers01Icon,
  LayoutThreeColumnIcon,
  LeftToRightListBulletIcon,
  LeftToRightListNumberIcon,
  LineIcon,
  Link02Icon,
  ListTreeIcon,
  Loading03Icon,
  MinimizeScreenIcon,
  MinusSignCircleIcon,
  MinusSignIcon,
  Moon02Icon,
  MoreHorizontalIcon,
  MoveIcon,
  PaintBoardIcon,
  PaintBucketIcon,
  ParagraphIcon,
  PathfinderMergeIcon,
  Pen01Icon,
  PencilEdit01Icon,
  PercentIcon,
  PlusSignCircleIcon,
  PolygonIcon,
  PrinterIcon,
  RadioButtonIcon,
  Redo02Icon,
  RefreshIcon,
  ReplaceIcon,
  RotateClockwiseIcon,
  Scissor01Icon,
  Search01Icon,
  SecondBracketIcon,
  Shield01Icon,
  SidebarLeftIcon,
  SidebarRightIcon,
  SignatureIcon,
  SlidersHorizontalIcon,
  SourceCodeSquareIcon,
  SparklesIcon,
  SplinePointerIcon,
  SquareIcon,
  SquareLock02Icon,
  SquareUnlock02Icon,
  StampIcon,
  StickyNote01Icon,
  Sun03Icon,
  Table01Icon,
  TableIcon,
  TableRowsSplitIcon,
  TextAlignCenterIcon,
  TextAlignJustifyLeftIcon,
  TextAlignLeft01Icon,
  TextAlignRight01Icon,
  TextBoldIcon,
  TextCenterlineCenterTopIcon,
  TextCheckIcon,
  TextColorIcon,
  TextFontIcon,
  TextIcon,
  TextItalicIcon,
  TextNumberSignIcon,
  TextStrikethroughIcon,
  TextSubscriptIcon,
  TextSuperscriptIcon,
  TextUnderlineIcon,
  TextWrapIcon,
  Tick02Icon,
  Undo02Icon,
  UnfoldMoreIcon,
  UngroupItemsIcon,
  Upload01Icon,
  ViewIcon,
  ViewOffIcon,
  WaveIcon,
  ZoomInAreaIcon,
} from "@hugeicons/core-free-icons"
import { HugeiconsIcon } from "@hugeicons/react"

// The registry source files reference icons through <IconPlaceholder /> with a
// per-library icon name for every icon library the shadcn CLI supports. At
// install time the CLI replaces the placeholder with the consumer's configured
// `iconLibrary` from components.json and strips this import. This component is
// only ever rendered on this docs site, which uses hugeicons.
//
// When a registry component starts using a new icon, add its hugeicons name
// here — the `hugeicons` prop is typed against this registry, so a missing
// entry is a type error.
const hugeicons = {
  Add01Icon,
  Alert01Icon,
  AlignBoxBottomCenterIcon,
  AlignBoxMiddleCenterIcon,
  AlignBoxTopCenterIcon,
  ArrowDown01Icon,
  ArrowDown02Icon,
  ArrowExpandDiagonal01Icon,
  ArrowExpandDiagonal02Icon,
  ArrowLeft01Icon,
  ArrowLeftRightIcon,
  ArrowRight01Icon,
  ArrowUp01Icon,
  ArrowUp02Icon,
  ArrowUpDownIcon,
  ArrowUpRight01Icon,
  Attachment01Icon,
  Book01Icon,
  BorderAll01Icon,
  BorderBottom01Icon,
  BorderHorizontalIcon,
  BorderInnerIcon,
  BorderLeft01Icon,
  BorderNone01Icon,
  BorderRight01Icon,
  BorderTop01Icon,
  BorderVerticalIcon,
  BrushIcon,
  BubbleChatIcon,
  Calendar03Icon,
  Camera01Icon,
  Cancel01Icon,
  CancelCircleIcon,
  CellsIcon,
  CheckIcon,
  CheckmarkCircle01Icon,
  CheckmarkSquare01Icon,
  ChevronDown,
  ChevronDownIcon,
  ChevronUp,
  CircleIcon,
  ClipboardIcon,
  ColumnsThreeCogIcon,
  Comment01Icon,
  CommentAdd01Icon,
  ColorPickerIcon,
  Copy01Icon,
  Cursor01Icon,
  CursorRectangleSelection01Icon,
  CursorTextIcon,
  Delete02Icon,
  DollarCircleIcon,
  Download01Icon,
  DragDropVerticalIcon,
  DropletOffIcon,
  EditOffIcon,
  ExternalLinkIcon,
  File01Icon,
  FileAddIcon,
  FileDiffIcon,
  FileImageIcon,
  FilePenIcon,
  FileSpreadsheetIcon,
  FileUploadIcon,
  FilterIcon,
  Flag01Icon,
  FloppyDiskIcon,
  FullScreenIcon,
  GalleryThumbnailsIcon,
  GitMergeIcon,
  Grid2X2XIcon,
  GridViewIcon,
  GroupItemsIcon,
  HandIcon,
  Heading01Icon,
  HighlighterIcon,
  Image01Icon,
  ImageAdd01Icon,
  ImageCompositionIcon,
  InformationCircleIcon,
  InputNumericIcon,
  InputTextIcon,
  Key01Icon,
  KeyboardIcon,
  Layers01Icon,
  LayoutThreeColumnIcon,
  LeftToRightListBulletIcon,
  LeftToRightListNumberIcon,
  LineIcon,
  Link02Icon,
  ListTreeIcon,
  Loading03Icon,
  MinimizeScreenIcon,
  MinusSignCircleIcon,
  MinusSignIcon,
  Moon02Icon,
  MoreHorizontalIcon,
  MoveIcon,
  PaintBoardIcon,
  PaintBucketIcon,
  ParagraphIcon,
  PathfinderMergeIcon,
  Pen01Icon,
  PencilEdit01Icon,
  PercentIcon,
  PlusSignCircleIcon,
  PolygonIcon,
  PrinterIcon,
  RadioButtonIcon,
  Redo02Icon,
  RefreshIcon,
  ReplaceIcon,
  RotateClockwiseIcon,
  Scissor01Icon,
  Search01Icon,
  SecondBracketIcon,
  Shield01Icon,
  SidebarLeftIcon,
  SidebarRightIcon,
  SignatureIcon,
  SlidersHorizontalIcon,
  SourceCodeSquareIcon,
  SparklesIcon,
  SplinePointerIcon,
  SquareIcon,
  SquareLock02Icon,
  SquareUnlock02Icon,
  StampIcon,
  StickyNote01Icon,
  Sun03Icon,
  Table01Icon,
  TableIcon,
  TableRowsSplitIcon,
  TextAlignCenterIcon,
  TextAlignJustifyLeftIcon,
  TextAlignLeft01Icon,
  TextAlignRight01Icon,
  TextBoldIcon,
  TextCenterlineCenterTopIcon,
  TextCheckIcon,
  TextColorIcon,
  TextFontIcon,
  TextIcon,
  TextItalicIcon,
  TextNumberSignIcon,
  TextStrikethroughIcon,
  TextSubscriptIcon,
  TextSuperscriptIcon,
  TextUnderlineIcon,
  TextWrapIcon,
  Tick02Icon,
  Undo02Icon,
  UnfoldMoreIcon,
  UngroupItemsIcon,
  Upload01Icon,
  ViewIcon,
  ViewOffIcon,
  WaveIcon,
  ZoomInAreaIcon,
}

export type IconPlaceholderProps = {
  lucide: string
  tabler: string
  hugeicons: keyof typeof hugeicons
  phosphor: string
  remixicon: string
} & React.ComponentProps<"svg">

export function IconPlaceholder(props: IconPlaceholderProps) {
  const {
    lucide: _lucide,
    tabler: _tabler,
    hugeicons: hugeiconsName,
    phosphor: _phosphor,
    remixicon: _remixicon,
    ...rest
  } = props

  return (
    <HugeiconsIcon
      icon={hugeicons[hugeiconsName]}
      {...(rest as Omit<React.ComponentProps<typeof HugeiconsIcon>, "icon">)}
    />
  )
}
author/apps/v4/lib/config.ts
파일 저장

const canonicalUrl = "https://www.extend.ai/ui"

export const siteConfig = {
  name: "Extend UI",
  url: canonicalUrl,
  ogImage: `${canonicalUrl}/opengraph-image.png`,
  description:
    "Open source UI primitives for building document processing products with viewers, review surfaces, and validation workflows.",
  links: {
    github: "https://github.com/extend-hq/ui",
  },
  navItems: [
    {
      href: "/docs",
      label: "Docs",
    },
    {
      href: "/docs/components",
      label: "Components",
    },
    {
      href: "/blocks",
      label: "Blocks",
    },
  ],
}

export const META_THEME_COLORS = {
  light: "#ffffff",
  dark: "#09090b",
}
author/apps/v4/components/ui/spinner.tsx
파일 저장

"use client"

export { Spinner } from "@/registry/new-york-v4/ui/spinner"
author/apps/v4/registry/new-york-v4/ui/input.tsx
파일 저장

"use client"

import type * as React from "react"
import { Input as InputPrimitive } from "@base-ui/react/input"

import { cn } from "@/lib/utils"

export type InputProps = Omit<
  InputPrimitive.Props & React.RefAttributes<HTMLInputElement>,
  "size"
> & {
  size?: "sm" | "default" | "lg" | number
  unstyled?: boolean
  nativeInput?: boolean
}

export function Input({
  className,
  size = "default",
  unstyled = false,
  nativeInput = false,
  style,
  ...props
}: InputProps): React.ReactElement {
  const inputClassName = cn(
    "h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none [transition:background-color_5000000s_ease-in-out_0s] placeholder:text-muted-foreground/72 sm:h-7.5 sm:leading-7.5",
    size === "sm" &&
      "h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5",
    size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5",
    props.type === "search" &&
      "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none",
    props.type === "file" &&
      "text-muted-foreground file:me-3 file:bg-transparent file:text-sm file:font-medium file:text-foreground"
  )

  return (
    <span
      className={
        cn(
          !unstyled &&
            "relative inline-flex w-full rounded-lg border border-input bg-popover text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-autofill:bg-foreground/4 has-focus-visible:border-ring has-focus-visible:ring-[3px] has-disabled:opacity-64 has-aria-invalid:border-destructive/36 has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none sm:text-sm dark:bg-input/32 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24",
          className
        ) || undefined
      }
      data-size={size}
      data-slot="input-control"
    >
      {nativeInput ? (
        <input
          className={inputClassName}
          data-slot="input"
          size={typeof size === "number" ? size : undefined}
          style={typeof style === "function" ? undefined : style}
          {...props}
        />
      ) : (
        <InputPrimitive
          className={inputClassName}
          data-slot="input"
          size={typeof size === "number" ? size : undefined}
          style={style}
          {...props}
        />
      )}
    </span>
  )
}

export { InputPrimitive }
author/apps/v4/registry/new-york-v4/ui/tabs.tsx
파일 저장

"use client"

import type React from "react"
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"

import { cn } from "@/lib/utils"

export type TabsVariant = "default" | "underline"

export function Tabs({
  className,
  ...props
}: TabsPrimitive.Root.Props): React.ReactElement {
  return (
    <TabsPrimitive.Root
      className={cn(
        "flex flex-col gap-2 data-[orientation=vertical]:flex-row",
        className
      )}
      data-slot="tabs"
      {...props}
    />
  )
}

export function TabsList({
  variant = "default",
  className,
  children,
  ...props
}: TabsPrimitive.List.Props & {
  variant?: TabsVariant
}): React.ReactElement {
  return (
    <TabsPrimitive.List
      className={cn(
        "relative z-0 flex w-fit items-center justify-center gap-x-0.5 text-muted-foreground",
        "data-[orientation=vertical]:flex-col",
        variant === "default"
          ? "rounded-lg bg-muted p-0.5 text-muted-foreground/72"
          : "data-[orientation=horizontal]:py-1 data-[orientation=vertical]:px-1 *:data-[slot=tabs-tab]:hover:bg-accent",
        className
      )}
      data-slot="tabs-list"
      {...props}
    >
      {children}
      <TabsPrimitive.Indicator
        className={cn(
          "absolute bottom-0 left-0 h-(--active-tab-height) w-(--active-tab-width) translate-x-(--active-tab-left) -translate-y-(--active-tab-bottom) transition-[width,translate] duration-200 ease-in-out",
          variant === "underline"
            ? "z-10 bg-primary data-[orientation=horizontal]:h-0.5 data-[orientation=horizontal]:translate-y-px data-[orientation=vertical]:w-0.5 data-[orientation=vertical]:-translate-x-px"
            : "-z-1 rounded-md bg-background shadow-sm/5 dark:bg-input"
        )}
        data-slot="tab-indicator"
      />
    </TabsPrimitive.List>
  )
}

export function TabsTab({
  className,
  ...props
}: TabsPrimitive.Tab.Props): React.ReactElement {
  return (
    <TabsPrimitive.Tab
      className={cn(
        "relative flex h-9 shrink-0 grow cursor-pointer items-center justify-center gap-1.5 rounded-md border border-transparent px-[calc(--spacing(2.5)-1px)] text-base font-medium whitespace-nowrap transition-[color,background-color,box-shadow] outline-none hover:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring data-[orientation=vertical]:w-full data-[orientation=vertical]:justify-start sm:h-8 sm:text-sm data-disabled:pointer-events-none data-disabled:opacity-64 data-active:text-foreground data-active:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4",
        className
      )}
      data-slot="tabs-tab"
      {...props}
    />
  )
}

export function TabsPanel({
  className,
  ...props
}: TabsPrimitive.Panel.Props): React.ReactElement {
  return (
    <TabsPrimitive.Panel
      className={cn("flex-1 outline-none", className)}
      data-slot="tabs-content"
      {...props}
    />
  )
}

export { TabsPrimitive, TabsTab as TabsTrigger, TabsPanel as TabsContent }
author/apps/v4/registry/new-york-v4/ui/tooltip.tsx
파일 저장

"use client"

import type React from "react"
import { isValidElement } from "react"
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"

import { cn } from "@/lib/utils"

export const TooltipCreateHandle: typeof TooltipPrimitive.createHandle =
  TooltipPrimitive.createHandle

export function TooltipProvider({
  delayDuration,
  delay = delayDuration,
  ...props
}: TooltipPrimitive.Provider.Props & {
  delayDuration?: TooltipPrimitive.Provider.Props["delay"]
}): React.ReactElement {
  return <TooltipPrimitive.Provider delay={delay} {...props} />
}

export const Tooltip: typeof TooltipPrimitive.Root = TooltipPrimitive.Root

export function TooltipTrigger({
  asChild,
  children,
  render,
  ...props
}: TooltipPrimitive.Trigger.Props & {
  asChild?: boolean
}): React.ReactElement {
  return (
    <TooltipPrimitive.Trigger
      data-slot="tooltip-trigger"
      render={
        render ??
        (asChild && isValidElement(children)
          ? (children as React.ReactElement<Record<string, unknown>>)
          : undefined)
      }
      {...props}
    >
      {asChild && isValidElement(children) ? undefined : children}
    </TooltipPrimitive.Trigger>
  )
}

export function TooltipPopup({
  className,
  align = "center",
  sideOffset = 4,
  side = "top",
  anchor,
  children,
  portalProps,
  ...props
}: TooltipPrimitive.Popup.Props & {
  align?: TooltipPrimitive.Positioner.Props["align"]
  side?: TooltipPrimitive.Positioner.Props["side"]
  sideOffset?: TooltipPrimitive.Positioner.Props["sideOffset"]
  anchor?: TooltipPrimitive.Positioner.Props["anchor"]
  portalProps?: TooltipPrimitive.Portal.Props
}): React.ReactElement {
  return (
    <TooltipPrimitive.Portal {...portalProps}>
      <TooltipPrimitive.Positioner
        align={align}
        anchor={anchor}
        className="z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none"
        data-slot="tooltip-positioner"
        side={side}
        sideOffset={sideOffset}
      >
        <TooltipPrimitive.Popup
          className={cn(
            "relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-md border bg-popover text-xs text-balance text-popover-foreground shadow-md/5 transition-[width,height,scale,opacity] not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-md)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:scale-98 data-ending-style:opacity-0 data-instant:duration-0 data-starting-style:scale-98 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
            className
          )}
          data-slot="tooltip-popup"
          {...props}
        >
          <TooltipPrimitive.Viewport
            className="relative size-full overflow-clip px-(--viewport-inline-padding) py-1 [--viewport-inline-padding:--spacing(2)] **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-current:transition-opacity **:data-current:data-ending-style:opacity-0 data-instant:transition-none **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:truncate **:data-previous:opacity-100 **:data-previous:transition-opacity **:data-previous:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-starting-style:opacity-0"
            data-slot="tooltip-viewport"
          >
            {children}
          </TooltipPrimitive.Viewport>
        </TooltipPrimitive.Popup>
      </TooltipPrimitive.Positioner>
    </TooltipPrimitive.Portal>
  )
}

export { TooltipPrimitive, TooltipPopup as TooltipContent }
author/apps/v4/registry/new-york-v4/ui/spinner.tsx
파일 저장

import type React from "react"

import { cn } from "@/lib/utils"
import { IconPlaceholder } from "@/components/icon-placeholder"

export function Spinner({
  className,
  ...props
}: React.ComponentProps<"svg">): React.ReactElement {
  return (
    <IconPlaceholder
      lucide="Loader2"
      tabler="IconLoader"
      hugeicons="Loading03Icon"
      phosphor="SpinnerIcon"
      remixicon="RiLoaderLine"
      aria-label="Loading"
      className={cn("animate-spin", className)}
      role="status"
      {...props}
    />
  )
}
author/apps/v4/app/globals.css
파일 저장

@import "tailwindcss";
@import "tw-animate-css";
@import "./shadcn-tailwind.css";
@import "./legacy-themes.css";

@import "../registry/styles/style-vega.css" layer(base);
@import "../registry/styles/style-nova.css" layer(base);
@import "../registry/styles/style-lyra.css" layer(base);
@import "../registry/styles/style-maia.css" layer(base);
@import "../registry/styles/style-mira.css" layer(base);
@import "../registry/styles/style-luma.css" layer(base);
@import "../registry/styles/style-sera.css" layer(base);

@custom-variant style-vega (&:where(.style-vega *));
@custom-variant style-nova (&:where(.style-nova *));
@custom-variant style-lyra (&:where(.style-lyra *));
@custom-variant style-maia (&:where(.style-maia *));
@custom-variant style-mira (&:where(.style-mira *));
@custom-variant style-luma (&:where(.style-luma *));
@custom-variant style-sera (&:where(.style-sera *));

@custom-variant dark (&:is(.dark *));
@custom-variant fixed (&:is(.layout-fixed *));

@theme inline {
  --breakpoint-3xl: 1600px;
  --breakpoint-4xl: 2000px;
  --font-sans: var(--font-sans);
  --font-heading: var(--font-heading);
  --font-mono: var(--font-mono);
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: calc(var(--radius) * 0.8);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) * 1.4);
  --radius-2xl: calc(var(--radius) * 1.8);
  --radius-3xl: calc(var(--radius) * 2.2);
  --radius-4xl: calc(var(--radius) * 2.6);
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-surface: var(--surface);
  --color-surface-foreground: var(--surface-foreground);
  --color-code: var(--code);
  --color-code-foreground: var(--code-foreground);
  --color-code-highlight: var(--code-highlight);
  --color-code-number: var(--code-number);
  --color-selection: var(--selection);
  --color-selection-foreground: var(--selection-foreground);
  --color-warning-foreground: var(--warning-foreground);
  --color-warning: var(--warning);
  --color-success-foreground: var(--success-foreground);
  --color-success: var(--success);
  --color-info-foreground: var(--info-foreground);
  --color-info: var(--info);
  --animate-skeleton: skeleton 2s -1s infinite linear;
  @keyframes skeleton {
    to {
      background-position: -200% 0;
    }
  }
}

:root {
  --radius: 0.625rem;
  --background: oklch(1 0 0);
  --foreground: var(--color-neutral-800);
  --card: var(--color-white);
  --card-foreground: var(--color-neutral-800);
  --popover: var(--color-white);
  --popover-foreground: var(--color-neutral-800);
  --primary: var(--color-neutral-800);
  --primary-foreground: var(--color-neutral-50);
  --secondary: --alpha(var(--color-black) / 4%);
  --secondary-foreground: var(--color-neutral-800);
  --muted: --alpha(var(--color-black) / 4%);
  --muted-foreground: color-mix(
    in srgb,
    var(--color-neutral-500) 90%,
    var(--color-black)
  );
  --accent: --alpha(var(--color-black) / 4%);
  --accent-foreground: var(--color-neutral-800);
  --destructive: var(--color-red-500);
  --destructive-foreground: var(--color-red-700);
  --border: --alpha(var(--color-black) / 8%);
  --input: --alpha(var(--color-black) / 10%);
  --ring: var(--color-neutral-400);
  --chart-1: var(--color-blue-300);
  --chart-2: var(--color-blue-500);
  --chart-3: var(--color-blue-600);
  --chart-4: var(--color-blue-700);
  --chart-5: var(--color-blue-800);
  --sidebar: var(--color-neutral-50);
  --sidebar-foreground: color-mix(
    in srgb,
    var(--color-neutral-800) 64%,
    var(--sidebar)
  );
  --sidebar-primary: var(--color-neutral-800);
  --sidebar-primary-foreground: var(--color-neutral-50);
  --sidebar-accent: --alpha(var(--color-black) / 4%);
  --sidebar-accent-foreground: var(--color-neutral-800);
  --sidebar-border: --alpha(var(--color-black) / 6%);
  --sidebar-ring: var(--color-neutral-400);
  --surface: oklch(0.98 0 0);
  --surface-foreground: var(--foreground);
  --code: var(--surface);
  --code-foreground: var(--surface-foreground);
  --code-highlight: oklch(0.96 0 0);
  --code-number: oklch(0.56 0 0);
  --selection: oklch(0.145 0 0);
  --selection-foreground: oklch(1 0 0);
  --info: var(--color-blue-500);
  --info-foreground: var(--color-blue-700);
  --success: var(--color-emerald-500);
  --success-foreground: var(--color-emerald-700);
  --warning: var(--color-amber-500);
  --warning-foreground: var(--color-amber-700);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: var(--color-neutral-100);
  --card: color-mix(in srgb, var(--background) 98%, var(--color-white));
  --card-foreground: var(--color-neutral-100);
  --popover: color-mix(in srgb, var(--background) 98%, var(--color-white));
  --popover-foreground: var(--color-neutral-100);
  --primary: var(--color-neutral-100);
  --primary-foreground: var(--color-neutral-800);
  --secondary: --alpha(var(--color-white) / 4%);
  --secondary-foreground: var(--color-neutral-100);
  --muted: --alpha(var(--color-white) / 4%);
  --muted-foreground: color-mix(
    in srgb,
    var(--color-neutral-500) 90%,
    var(--color-white)
  );
  --accent: --alpha(var(--color-white) / 4%);
  --accent-foreground: var(--color-neutral-100);
  --destructive: color-mix(
    in srgb,
    var(--color-red-500) 90%,
    var(--color-white)
  );
  --destructive-foreground: var(--color-red-400);
  --border: --alpha(var(--color-white) / 6%);
  --input: --alpha(var(--color-white) / 8%);
  --ring: var(--color-neutral-500);
  --chart-1: var(--color-blue-300);
  --chart-2: var(--color-blue-500);
  --chart-3: var(--color-blue-600);
  --chart-4: var(--color-blue-700);
  --chart-5: var(--color-blue-800);
  --sidebar: color-mix(
    in srgb,
    var(--color-neutral-950) 97%,
    var(--color-white)
  );
  --sidebar-foreground: color-mix(
    in srgb,
    var(--color-neutral-100) 64%,
    var(--sidebar)
  );
  --sidebar-primary: var(--color-neutral-100);
  --sidebar-primary-foreground: var(--color-neutral-800);
  --sidebar-accent: --alpha(var(--color-white) / 4%);
  --sidebar-accent-foreground: var(--color-neutral-100);
  --sidebar-border: --alpha(var(--color-white) / 5%);
  --sidebar-ring: var(--color-neutral-400);
  --surface: oklch(0.2 0 0);
  --surface-foreground: oklch(0.708 0 0);
  --code: var(--surface);
  --code-foreground: var(--surface-foreground);
  --code-highlight: oklch(0.27 0 0);
  --code-number: oklch(0.72 0 0);
  --selection: oklch(0.922 0 0);
  --selection-foreground: oklch(0.205 0 0);
  --info: var(--color-blue-500);
  --info-foreground: var(--color-blue-400);
  --success: var(--color-emerald-500);
  --success-foreground: var(--color-emerald-400);
  --warning: var(--color-amber-500);
  --warning-foreground: var(--color-amber-400);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  ::selection {
    @apply bg-selection text-selection-foreground;
  }
  html {
    @apply overscroll-y-none;
  }
  body {
    font-synthesis-weight: none;
    text-rendering: optimizeLegibility;
  }
  .cn-font-heading {
    @apply font-heading;
  }

  [data-slot="layout"] {
    @apply overscroll-none;
  }

  @supports (font: -apple-system-body) and (-webkit-appearance: none) {
    [data-wrapper] {
      @apply min-[1800px]:border-t;
    }
  }

  a:active,
  button:active {
    @apply opacity-60 md:opacity-100;
  }

  [data-lang="ar"] {
    font-family: var(--font-ar);
  }

  [data-lang="he"] {
    font-family: var(--font-he);
  }
}

@utility border-grid {
  @apply border-border/50 dark:border-border;
}

@utility section-soft {
  @apply bg-linear-to-b from-background to-surface/40 dark:bg-background 3xl:fixed:bg-none;
}

@utility theme-container {
  @apply font-sans;
}

@utility container-wrapper {
  @apply mx-auto w-full px-2 3xl:fixed:max-w-[calc(var(--breakpoint-2xl)+2rem)];
}

@utility container {
  @apply mx-auto max-w-[1400px] px-4 3xl:max-w-screen-2xl lg:px-8;
}

@utility no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;

  &::-webkit-scrollbar {
    display: none;
  }
}

@utility scrollbar-overflow-only {
  &:not(:has([data-slot="scroll-area-viewport"][data-has-overflow-x]))
    [data-slot="scroll-area-scrollbar"][data-orientation="horizontal"] {
    display: none;
  }

  &:not(:has([data-slot="scroll-area-viewport"][data-has-overflow-y]))
    [data-slot="scroll-area-scrollbar"][data-orientation="vertical"] {
    display: none;
  }
}

@utility border-ghost {
  @apply relative after:absolute after:inset-0 after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten;
}

@utility step {
  counter-increment: step;

  &:before {
    @apply mr-2 inline-flex size-6 items-center justify-center rounded-full border-background bg-background text-center -indent-px font-mono text-sm font-medium md:absolute md:mt-[-4px] md:ml-[-50px] md:size-9 md:border-4;
    box-shadow: inset 0 0 0 9999px var(--muted);
    content: counter(step);
  }
}

@utility extend-touch-target {
  @media (pointer: coarse) {
    @apply relative touch-manipulation after:absolute after:-inset-2;
  }
}

@layer components {
  .steps {
    &:first-child {
      @apply !mt-0;
    }

    &:first-child > h3:first-child {
      @apply !mt-0;
    }

    > h3 {
      @apply !mt-8;
    }

    > h3 + p {
      @apply !mt-2;
    }
  }

  .docs-view-code-button {
    background-color: var(--background) !important;
    color: var(--foreground);
    box-shadow: 0 1px 2px rgb(0 0 0 / 6%) !important;
    transition-property: border-color, box-shadow, color;
  }

  .docs-view-code-button:hover,
  .docs-view-code-button[data-pressed] {
    background-color: var(--background) !important;
    border-color: color-mix(in oklab, var(--ring) 55%, transparent) !important;
    box-shadow:
      0 4px 14px rgb(0 0 0 / 10%),
      0 0 0 3px color-mix(in oklab, var(--ring) 16%, transparent) !important;
  }

  .dark .docs-view-code-button {
    background-color: var(--background) !important;
    box-shadow: 0 1px 2px rgb(0 0 0 / 30%) !important;
  }

  .dark .docs-view-code-button:hover,
  .dark .docs-view-code-button[data-pressed] {
    background-color: var(--background) !important;
    box-shadow:
      0 4px 14px rgb(0 0 0 / 36%),
      0 0 0 3px color-mix(in oklab, var(--ring) 20%, transparent) !important;
  }

  [data-rehype-pretty-code-figure] {
    background-color: var(--color-code);
    color: var(--color-code-foreground);
    border-radius: var(--radius-xl);
    border-width: 0px;
    border-color: var(--border);
    margin-top: calc(var(--spacing) * 6);
    overflow: hidden;
    font-size: var(--text-sm);
    outline: none;
    position: relative;
    @apply -mx-1 md:-mx-1;

    &:has([data-rehype-pretty-code-title]) [data-slot="copy-button"] {
      top: calc(var(--spacing) * 1.5) !important;
    }
  }

  [data-rehype-pretty-code-title] {
    border-bottom: color-mix(in oklab, var(--border) 30%, transparent);
    border-bottom-width: 1px;
    border-bottom-style: solid;
    padding-block: calc(var(--spacing) * 2.5);
    padding-inline: calc(var(--spacing) * 4);
    font-size: var(--text-sm);
    font-family: var(--font-mono);
    color: var(--color-code-foreground);
  }

  [data-line-numbers] {
    display: grid;
    min-width: 100%;
    white-space: pre;
    border: 0;
    background: transparent;
    padding: 0;
    counter-reset: line;
    box-decoration-break: clone;
  }

  [data-line-numbers] [data-line]::before {
    font-size: var(--text-sm);
    counter-increment: line;
    content: counter(line);
    display: inline-block;
    width: calc(var(--spacing) * 16);
    padding-right: calc(var(--spacing) * 6);
    text-align: right;
    color: var(--color-code-number);
    background-color: var(--color-code);
    position: sticky;
    left: 0;
  }

  [data-line-numbers] [data-highlighted-line][data-line]::before {
    background-color: var(--color-code-highlight);
  }

  [data-line] {
    padding-top: calc(var(--spacing) * 0.5);
    padding-bottom: calc(var(--spacing) * 0.5);
    min-height: calc(var(--spacing) * 1);
    width: 100%;
    display: inline-block;
  }

  /*
   * ```text composition trees use box-drawing characters; per-line padding makes
   * vertical connectors look broken. rehype-pretty-code sets `data-language` on
   * `pre`/`code` (not `language-*` classes). It also sets `code { display: grid }`,
   * which can add visible row separation — reset to a normal pre stack for text.
   */
  [data-rehype-pretty-code-figure] pre[data-language="text"] code,
  [data-rehype-pretty-code-figure] pre[data-language="plaintext"] code,
  [data-slot="docs"] pre[data-language="text"] code,
  [data-slot="docs"] pre[data-language="plaintext"] code {
    display: block !important;
    white-space: pre;
    line-height: 0.95;
    font-family:
      ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
      "Courier New", monospace;
    font-variant-ligatures: none;
  }

  [data-rehype-pretty-code-figure] pre[data-language="text"] [data-line],
  [data-rehype-pretty-code-figure] pre[data-language="plaintext"] [data-line],
  [data-rehype-pretty-code-figure] code[data-language="text"] [data-line],
  [data-rehype-pretty-code-figure] code[data-language="plaintext"] [data-line],
  [data-slot="docs"] pre[data-language="text"] [data-line],
  [data-slot="docs"] pre[data-language="plaintext"] [data-line] {
    padding-top: 0;
    padding-bottom: 0;
    min-height: unset;
    line-height: 0.95;
    display: block;
  }

  [data-line] span {
    color: var(--shiki-light);

    @variant dark {
      color: var(--shiki-dark) !important;
    }
  }

  [data-highlighted-line],
  [data-highlighted-chars] {
    position: relative;
    background-color: var(--color-code-highlight);
  }

  [data-highlighted-line] {
    &:after {
      position: absolute;
      top: 0;
      left: 0;
      width: 2px;
      height: 100%;
      content: "";
      background-color: color-mix(
        in oklab,
        var(--muted-foreground) 50%,
        transparent
      );
    }
  }

  [data-highlighted-chars] {
    border-radius: var(--radius-sm);
    padding-inline: 0.3rem;
    padding-block: 0.1rem;
    font-family: var(--font-mono);
    font-size: 0.8rem;
  }
}

@layer components {
  .dialog-ring {
    @apply rounded-xl border-none bg-clip-padding shadow-2xl ring-4 ring-neutral-200/80 dark:bg-neutral-900 dark:ring-neutral-800;
  }
}
author/apps/v4/app/shadcn-tailwind.css
파일 저장

@theme inline {
  @keyframes accordion-down {
    from {
      height: 0;
    }
    to {
      height: var(
        --radix-accordion-content-height,
        var(--accordion-panel-height, auto)
      );
    }
  }

  @keyframes accordion-up {
    from {
      height: var(
        --radix-accordion-content-height,
        var(--accordion-panel-height, auto)
      );
    }
    to {
      height: 0;
    }
  }
}

/* Custom variants */
@custom-variant data-open {
  &:where([data-state="open"]),
  &:where([data-open]:not([data-open="false"])) {
    @slot;
  }
}

@custom-variant data-closed {
  &:where([data-state="closed"]),
  &:where([data-closed]:not([data-closed="false"])) {
    @slot;
  }
}

@custom-variant data-checked {
  &:where([data-state="checked"]),
  &:where([data-checked]:not([data-checked="false"])) {
    @slot;
  }
}

@custom-variant data-unchecked {
  &:where([data-state="unchecked"]),
  &:where([data-unchecked]:not([data-unchecked="false"])) {
    @slot;
  }
}

@custom-variant data-selected {
  &:where([data-selected="true"]) {
    @slot;
  }
}

@custom-variant data-disabled {
  &:where([data-disabled="true"]),
  &:where([data-disabled]:not([data-disabled="false"])) {
    @slot;
  }
}

@custom-variant data-active {
  &:where([data-state="active"]),
  &:where([data-active]:not([data-active="false"])) {
    @slot;
  }
}

@custom-variant data-horizontal {
  &:where([data-orientation="horizontal"]) {
    @slot;
  }
}

@custom-variant data-vertical {
  &:where([data-orientation="vertical"]) {
    @slot;
  }
}

@utility no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;

  &::-webkit-scrollbar {
    display: none;
  }
}
author/apps/v4/package.json
파일 저장

{
  "name": "v4",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev --turbopack --port 4000",
    "build": "pnpm registry:build && next build",
    "start": "next start --port 4000",
    "preview": "next build && next start --port 4000",
    "lint": "eslint . --cache --cache-location .eslintcache",
    "lint:fix": "eslint --fix . --cache --cache-location .eslintcache",
    "typecheck": "tsc --noEmit",
    "format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
    "format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
    "registry:build": "node scripts/build-registry.mts",
    "registry:check-extend-ui": "node scripts/check-extend-ui-registry.mts",
    "registry:test": "node --test scripts/registry-source.test.mts",
    "registry:validate": "shadcn registry validate",
    "postinstall": "fumadocs-mdx"
  },
  "dependencies": {
    "@base-ui/react": "^1.4.1",
    "@dnd-kit/core": "^6.3.1",
    "@dnd-kit/sortable": "^10.0.0",
    "@dnd-kit/utilities": "^3.2.2",
    "@embedpdf/core": "^2.15.0",
    "@embedpdf/default-stamps": "0.0.1",
    "@embedpdf/engines": "^2.15.0",
    "@embedpdf/models": "^2.15.0",
    "@embedpdf/plugin-annotation": "2.15.0",
    "@embedpdf/plugin-attachment": "2.15.0",
    "@embedpdf/plugin-bookmark": "2.15.0",
    "@embedpdf/plugin-capture": "2.15.0",
    "@embedpdf/plugin-document-manager": "^2.15.0",
    "@embedpdf/plugin-export": "2.15.0",
    "@embedpdf/plugin-form": "2.15.0",
    "@embedpdf/plugin-fullscreen": "2.15.0",
    "@embedpdf/plugin-history": "2.15.0",
    "@embedpdf/plugin-interaction-manager": "^2.15.0",
    "@embedpdf/plugin-pan": "2.15.0",
    "@embedpdf/plugin-print": "2.15.0",
    "@embedpdf/plugin-redaction": "2.15.0",
    "@embedpdf/plugin-render": "^2.15.0",
    "@embedpdf/plugin-rotate": "^2.15.0",
    "@embedpdf/plugin-scroll": "^2.15.0",
    "@embedpdf/plugin-search": "^2.15.0",
    "@embedpdf/plugin-selection": "^2.15.0",
    "@embedpdf/plugin-signature": "2.15.0",
    "@embedpdf/plugin-spread": "2.15.0",
    "@embedpdf/plugin-stamp": "2.15.0",
    "@embedpdf/plugin-thumbnail": "^2.15.0",
    "@embedpdf/plugin-tiling": "^2.15.0",
    "@embedpdf/plugin-view-manager": "2.15.0",
    "@embedpdf/plugin-viewport": "^2.15.0",
    "@embedpdf/plugin-zoom": "^2.15.0",
    "@embedpdf/utils": "2.15.0",
    "@extend-ai/react-docx": "0.9.2",
    "@extend-ai/react-pptx": "0.2.1",
    "@extend-ai/react-xlsx": "0.16.4",
    "@glideapps/glide-data-grid": "6.0.4-alpha24",
    "@hugeicons/core-free-icons": "^4.2.0",
    "@hugeicons/react": "^1.1.6",
    "@paper-design/shaders-react": "^0.0.76",
    "@pierre/diffs": "^1.2.7",
    "@pierre/trees": "1.0.0-beta.4",
    "@radix-ui/react-hover-card": "^1.1.23",
    "@radix-ui/react-popover": "^1.1.23",
    "@radix-ui/react-scroll-area": "^1.2.18",
    "@radix-ui/react-select": "^2.3.7",
    "@tabler/icons-react": "^3.31.0",
    "@tanstack/react-virtual": "3.13.26",
    "@vercel/analytics": "^2.0.1",
    "border-beam": "^1.0.1",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "fumadocs-core": "16.0.5",
    "fumadocs-mdx": "13.0.2",
    "jotai": "^2.15.0",
    "lodash": "^4.18.1",
    "lru-cache": "^11.2.4",
    "marked": "^16.0.10",
    "next": "16.3.3",
    "next-themes": "0.4.6",
    "nuqs": "^2.8.9",
    "papaparse": "^5.5.3",
    "pdf-lib": "^1.17.1",
    "radix-ui": "^1.4.3",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "react-markdown": "^10.1.0",
    "react-resizable-panels": "^4.11.2",
    "react-responsive-carousel": "^3.2.23",
    "rehype-pretty-code": "^0.14.1",
    "rehype-raw": "^7.0.0",
    "rehype-sanitize": "^6.0.0",
    "remark-gfm": "4.0.1",
    "shadcn": "^4.8.2",
    "shiki": "^1.10.1",
    "signature_pad": "^5.1.3",
    "sonner": "^2.0.0",
    "tailwind-merge": "^3.3.1",
    "zod": "^3.25.76"
  },
  "devDependencies": {
    "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
    "@tailwindcss/postcss": "^4.2.4",
    "@types/node": "^20",
    "@types/papaparse": "^5.5.2",
    "@types/react": "19.2.2",
    "@types/react-dom": "19.2.2",
    "babel-plugin-react-compiler": "^1.0.0",
    "eslint": "^9",
    "eslint-config-next": "16.2.10",
    "eslint-plugin-react-hooks": "7.1.1",
    "prettier": "^3.4.2",
    "prettier-plugin-tailwindcss": "^0.7.2",
    "tailwindcss": "^4",
    "ts-morph": "27.0.2",
    "tw-animate-css": "^1.4.0",
    "typescript": "^5",
    "typescript-eslint": "^8.59.1"
  },
  "prettier": {
    "endOfLine": "lf",
    "semi": false,
    "singleQuote": false,
    "tabWidth": 2,
    "trailingComma": "es5",
    "importOrder": [
      "^(react/(.*)$)|^(react$)",
      "^(next/(.*)$)|^(next$)",
      "<THIRD_PARTY_MODULES>",
      "",
      "^types$",
      "^@/types/(.*)$",
      "^@/config/(.*)$",
      "^@/lib/(.*)$",
      "^@/hooks/(.*)$",
      "^@/components/ui/(.*)$",
      "^@/components/(.*)$",
      "^@/registry/(.*)$",
      "^@/styles/(.*)$",
      "^@/app/(.*)$",
      "^@/www/(.*)$",
      "",
      "^[./]"
    ],
    "importOrderParserPlugins": [
      "typescript",
      "jsx",
      "decorators-legacy"
    ],
    "tailwindStylesheet": "./app/globals.css",
    "tailwindFunctions": [
      "cn",
      "cva"
    ],
    "plugins": [
      "@ianvs/prettier-plugin-sort-imports",
      "prettier-plugin-tailwindcss"
    ]
  }
}
Usage.tsx실행 안내·자료
파일 저장

// Local host for the unchanged exact baseline demonstration.
import OriginalDemo from "./baseline-example.tsx";
export default function Demo() { return <><OriginalDemo /></>; }
runtime/author-mount.tsx실행 안내·자료
파일 저장

/** Local sandbox host. Ready is a mount observation, never a verification result. */
import React, { Component, useEffect } from 'react';
import { createRoot } from 'react-dom/client';

declare global {
  interface Window {
    __STYLEGALLERY_PREVIEW__: { id: string; status: string; errors: string[] };
  }
}

export function mount(Demo: React.ComponentType, id: string) {
  const state = window.__STYLEGALLERY_PREVIEW__ = { id, status: 'loading', errors: [] as string[] };
  const send = (message: object) => parent.postMessage({ ...message, id }, '*');
  const report = (error: unknown) => {
    const message = error instanceof Error ? error.message : String(error);
    if (!state.errors.includes(message)) state.errors.push(message);
    state.status = 'error';
    document.body.dataset.previewStatus = 'error';
    send({ type: 'sg-preview-error', message });
  };
  window.addEventListener('error', (event) => report(event.error ?? event.message));
  window.addEventListener('unhandledrejection', (event) => report(event.reason));
  window.addEventListener('securitypolicyviolation', (event) => report(`Blocked by preview policy: ${event.violatedDirective}`));
  const theme = new URLSearchParams(location.search).get('theme') === 'dark' ? 'dark' : 'light';
  document.documentElement.classList.toggle('dark', theme === 'dark');
  document.documentElement.dataset.theme = theme;
  // Keep original source bytes while ensuring imported attribution/navigation remains local text.
  const removeDestinations = () => document.querySelectorAll('a[href]').forEach((link) => {
    link.removeAttribute('href');
    link.removeAttribute('target');
  });
  document.addEventListener('click', (event) => {
    if ((event.target as Element)?.closest?.('a')) event.preventDefault();
  }, true);
  document.addEventListener('submit', (event) => event.preventDefault());
  new MutationObserver(removeDestinations).observe(document.getElementById('root')!, { childList: true, subtree: true });
  const observe = () => {
    const root = document.getElementById('root')!;
    const elements = Array.from(root.querySelectorAll('*'));
    const rect = root.getBoundingClientRect();
    const diagnostics = {
      textLength: (root.textContent ?? '').trim().length,
      elementCount: elements.length,
      visibleElementCount: elements.filter((element) => {
        const bounds = element.getBoundingClientRect();
        const style = getComputedStyle(element);
        return bounds.width > 0 && bounds.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0;
      }).length,
      images: [],
      canvases: [],
      rootRect: { width: rect.width, height: rect.height },
      documentWidth: document.documentElement.scrollWidth,
      viewportWidth: innerWidth,
    };
    send({ type: 'sg-preview-observation', diagnostics });
  };
  class Boundary extends Component<{ children: React.ReactNode }, { error: string | null }> {
    state = { error: null as string | null };
    static getDerivedStateFromError(error: Error) { return { error: error.message }; }
    componentDidCatch(error: Error) { report(error); }
    render() { return this.state.error ? <p role="alert">This preview could not render: {this.state.error}</p> : this.props.children; }
  }
  function Ready() {
    useEffect(() => {
      const preference = matchMedia('(prefers-reduced-motion: reduce)');
      const syncSVG = () => document.querySelectorAll('svg').forEach((svg) => {
        if (preference.matches) svg.pauseAnimations?.();
        else svg.unpauseAnimations?.();
      });
      syncSVG();
      preference.addEventListener('change', syncSVG);
      let second = 0;
      let delayed = 0;
      const first = requestAnimationFrame(() => { second = requestAnimationFrame(() => {
        if (state.status !== 'error') {
          state.status = 'mounted';
          document.body.dataset.previewStatus = 'mounted';
          send({ type: 'sg-preview-ready' });
          observe();
          delayed = window.setTimeout(observe, 700);
        }
      }); });
      return () => {
        cancelAnimationFrame(first);
        cancelAnimationFrame(second);
        clearTimeout(delayed);
        preference.removeEventListener('change', syncSVG);
      };
    }, []);
    return <Demo />;
  }
  createRoot(document.getElementById('root')!).render(<Boundary><Ready /></Boundary>);
}
runtime/author-styles.css실행 안내·자료
파일 저장

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--background);
  --color-card-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--muted);
  --color-secondary-foreground: var(--foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--muted);
  --color-accent-foreground: var(--foreground);
  --color-popover: var(--background);
  --color-popover-foreground: var(--foreground);
  --color-destructive: #dc2626;
  --color-destructive-foreground: #ffffff;
  --color-input: var(--border);
  --color-border: var(--border);
  --color-ring: var(--foreground);
  --radius-lg: 0.5rem;
  --radius-md: 0.375rem;
  --radius-sm: 0.25rem;
}

:root {
  --background: #ffffff;
  --border: #d4d4d8;
  --foreground: #18181b;
  --muted: #f4f4f5;
  --muted-foreground: #71717a;
  --primary: #18181b;
  --primary-foreground: #fafafa;
  background: var(--background);
  color: var(--foreground);
  color-scheme: light;
  font-family: Arial, sans-serif;
}

.dark {
  --background: #09090b;
  --border: #3f3f46;
  --foreground: #fafafa;
  --muted: #27272a;
  --muted-foreground: #a1a1aa;
  --primary: #fafafa;
  --primary-foreground: #18181b;
  color-scheme: dark;
}

body {
  margin: 0;
  min-height: 100vh;
}

#root {
  align-items: center;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
  width: 100%;
}

#root > * {
  max-width: 100%;
}

noscript {
  display: block;
  padding: 24px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-play-state: paused !important;
  }
}
runtime/applied-theme.css실행 안내·자료
파일 저장

@custom-variant style-vega (&:where(.style-vega *));
@custom-variant style-nova (&:where(.style-nova *));
@custom-variant style-lyra (&:where(.style-lyra *));
@custom-variant style-maia (&:where(.style-maia *));
@custom-variant style-mira (&:where(.style-mira *));
@custom-variant style-luma (&:where(.style-luma *));
@custom-variant style-sera (&:where(.style-sera *));

@custom-variant dark (&:is(.dark *));
@custom-variant fixed (&:is(.layout-fixed *));

@theme inline {
  --breakpoint-3xl: 1600px;
  --breakpoint-4xl: 2000px;
  --font-sans: var(--font-sans);
  --font-heading: var(--font-heading);
  --font-mono: var(--font-mono);
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: calc(var(--radius) * 0.8);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) * 1.4);
  --radius-2xl: calc(var(--radius) * 1.8);
  --radius-3xl: calc(var(--radius) * 2.2);
  --radius-4xl: calc(var(--radius) * 2.6);
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-surface: var(--surface);
  --color-surface-foreground: var(--surface-foreground);
  --color-code: var(--code);
  --color-code-foreground: var(--code-foreground);
  --color-code-highlight: var(--code-highlight);
  --color-code-number: var(--code-number);
  --color-selection: var(--selection);
  --color-selection-foreground: var(--selection-foreground);
  --color-warning-foreground: var(--warning-foreground);
  --color-warning: var(--warning);
  --color-success-foreground: var(--success-foreground);
  --color-success: var(--success);
  --color-info-foreground: var(--info-foreground);
  --color-info: var(--info);
  --animate-skeleton: skeleton 2s -1s infinite linear;
  @keyframes skeleton {
    to {
      background-position: -200% 0;
    }
  }
}

:root {
  --radius: 0.625rem;
  --background: oklch(1 0 0);
  --foreground: var(--color-neutral-800);
  --card: var(--color-white);
  --card-foreground: var(--color-neutral-800);
  --popover: var(--color-white);
  --popover-foreground: var(--color-neutral-800);
  --primary: var(--color-neutral-800);
  --primary-foreground: var(--color-neutral-50);
  --secondary: --alpha(var(--color-black) / 4%);
  --secondary-foreground: var(--color-neutral-800);
  --muted: --alpha(var(--color-black) / 4%);
  --muted-foreground: color-mix(
    in srgb,
    var(--color-neutral-500) 90%,
    var(--color-black)
  );
  --accent: --alpha(var(--color-black) / 4%);
  --accent-foreground: var(--color-neutral-800);
  --destructive: var(--color-red-500);
  --destructive-foreground: var(--color-red-700);
  --border: --alpha(var(--color-black) / 8%);
  --input: --alpha(var(--color-black) / 10%);
  --ring: var(--color-neutral-400);
  --chart-1: var(--color-blue-300);
  --chart-2: var(--color-blue-500);
  --chart-3: var(--color-blue-600);
  --chart-4: var(--color-blue-700);
  --chart-5: var(--color-blue-800);
  --sidebar: var(--color-neutral-50);
  --sidebar-foreground: color-mix(
    in srgb,
    var(--color-neutral-800) 64%,
    var(--sidebar)
  );
  --sidebar-primary: var(--color-neutral-800);
  --sidebar-primary-foreground: var(--color-neutral-50);
  --sidebar-accent: --alpha(var(--color-black) / 4%);
  --sidebar-accent-foreground: var(--color-neutral-800);
  --sidebar-border: --alpha(var(--color-black) / 6%);
  --sidebar-ring: var(--color-neutral-400);
  --surface: oklch(0.98 0 0);
  --surface-foreground: var(--foreground);
  --code: var(--surface);
  --code-foreground: var(--surface-foreground);
  --code-highlight: oklch(0.96 0 0);
  --code-number: oklch(0.56 0 0);
  --selection: oklch(0.145 0 0);
  --selection-foreground: oklch(1 0 0);
  --info: var(--color-blue-500);
  --info-foreground: var(--color-blue-700);
  --success: var(--color-emerald-500);
  --success-foreground: var(--color-emerald-700);
  --warning: var(--color-amber-500);
  --warning-foreground: var(--color-amber-700);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: var(--color-neutral-100);
  --card: color-mix(in srgb, var(--background) 98%, var(--color-white));
  --card-foreground: var(--color-neutral-100);
  --popover: color-mix(in srgb, var(--background) 98%, var(--color-white));
  --popover-foreground: var(--color-neutral-100);
  --primary: var(--color-neutral-100);
  --primary-foreground: var(--color-neutral-800);
  --secondary: --alpha(var(--color-white) / 4%);
  --secondary-foreground: var(--color-neutral-100);
  --muted: --alpha(var(--color-white) / 4%);
  --muted-foreground: color-mix(
    in srgb,
    var(--color-neutral-500) 90%,
    var(--color-white)
  );
  --accent: --alpha(var(--color-white) / 4%);
  --accent-foreground: var(--color-neutral-100);
  --destructive: color-mix(
    in srgb,
    var(--color-red-500) 90%,
    var(--color-white)
  );
  --destructive-foreground: var(--color-red-400);
  --border: --alpha(var(--color-white) / 6%);
  --input: --alpha(var(--color-white) / 8%);
  --ring: var(--color-neutral-500);
  --chart-1: var(--color-blue-300);
  --chart-2: var(--color-blue-500);
  --chart-3: var(--color-blue-600);
  --chart-4: var(--color-blue-700);
  --chart-5: var(--color-blue-800);
  --sidebar: color-mix(
    in srgb,
    var(--color-neutral-950) 97%,
    var(--color-white)
  );
  --sidebar-foreground: color-mix(
    in srgb,
    var(--color-neutral-100) 64%,
    var(--sidebar)
  );
  --sidebar-primary: var(--color-neutral-100);
  --sidebar-primary-foreground: var(--color-neutral-800);
  --sidebar-accent: --alpha(var(--color-white) / 4%);
  --sidebar-accent-foreground: var(--color-neutral-100);
  --sidebar-border: --alpha(var(--color-white) / 5%);
  --sidebar-ring: var(--color-neutral-400);
  --surface: oklch(0.2 0 0);
  --surface-foreground: oklch(0.708 0 0);
  --code: var(--surface);
  --code-foreground: var(--surface-foreground);
  --code-highlight: oklch(0.27 0 0);
  --code-number: oklch(0.72 0 0);
  --selection: oklch(0.922 0 0);
  --selection-foreground: oklch(0.205 0 0);
  --info: var(--color-blue-500);
  --info-foreground: var(--color-blue-400);
  --success: var(--color-emerald-500);
  --success-foreground: var(--color-emerald-400);
  --warning: var(--color-amber-500);
  --warning-foreground: var(--color-amber-400);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  ::selection {
    @apply bg-selection text-selection-foreground;
  }
  html {
    @apply overscroll-y-none;
  }
  body {
    font-synthesis-weight: none;
    text-rendering: optimizeLegibility;
  }
  .cn-font-heading {
    @apply font-heading;
  }

  [data-slot="layout"] {
    @apply overscroll-none;
  }

  @supports (font: -apple-system-body) and (-webkit-appearance: none) {
    [data-wrapper] {
      @apply min-[1800px]:border-t;
    }
  }

  a:active,
  button:active {
    @apply opacity-60 md:opacity-100;
  }

  [data-lang="ar"] {
    font-family: var(--font-ar);
  }

  [data-lang="he"] {
    font-family: var(--font-he);
  }
}

@utility border-grid {
  @apply border-border/50 dark:border-border;
}

@utility section-soft {
  @apply bg-linear-to-b from-background to-surface/40 dark:bg-background 3xl:fixed:bg-none;
}

@utility theme-container {
  @apply font-sans;
}

@utility container-wrapper {
  @apply mx-auto w-full px-2 3xl:fixed:max-w-[calc(var(--breakpoint-2xl)+2rem)];
}

@utility container {
  @apply mx-auto max-w-[1400px] px-4 3xl:max-w-screen-2xl lg:px-8;
}

@utility no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;

  &::-webkit-scrollbar {
    display: none;
  }
}

@utility scrollbar-overflow-only {
  &:not(:has([data-slot="scroll-area-viewport"][data-has-overflow-x]))
    [data-slot="scroll-area-scrollbar"][data-orientation="horizontal"] {
    display: none;
  }

  &:not(:has([data-slot="scroll-area-viewport"][data-has-overflow-y]))
    [data-slot="scroll-area-scrollbar"][data-orientation="vertical"] {
    display: none;
  }
}

@utility border-ghost {
  @apply relative after:absolute after:inset-0 after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten;
}

@utility step {
  counter-increment: step;

  &:before {
    @apply mr-2 inline-flex size-6 items-center justify-center rounded-full border-background bg-background text-center -indent-px font-mono text-sm font-medium md:absolute md:mt-[-4px] md:ml-[-50px] md:size-9 md:border-4;
    box-shadow: inset 0 0 0 9999px var(--muted);
    content: counter(step);
  }
}

@utility extend-touch-target {
  @media (pointer: coarse) {
    @apply relative touch-manipulation after:absolute after:-inset-2;
  }
}

@layer components {
  .steps {
    &:first-child {
      @apply !mt-0;
    }

    &:first-child > h3:first-child {
      @apply !mt-0;
    }

    > h3 {
      @apply !mt-8;
    }

    > h3 + p {
      @apply !mt-2;
    }
  }

  .docs-view-code-button {
    background-color: var(--background) !important;
    color: var(--foreground);
    box-shadow: 0 1px 2px rgb(0 0 0 / 6%) !important;
    transition-property: border-color, box-shadow, color;
  }

  .docs-view-code-button:hover,
  .docs-view-code-button[data-pressed] {
    background-color: var(--background) !important;
    border-color: color-mix(in oklab, var(--ring) 55%, transparent) !important;
    box-shadow:
      0 4px 14px rgb(0 0 0 / 10%),
      0 0 0 3px color-mix(in oklab, var(--ring) 16%, transparent) !important;
  }

  .dark .docs-view-code-button {
    background-color: var(--background) !important;
    box-shadow: 0 1px 2px rgb(0 0 0 / 30%) !important;
  }

  .dark .docs-view-code-button:hover,
  .dark .docs-view-code-button[data-pressed] {
    background-color: var(--background) !important;
    box-shadow:
      0 4px 14px rgb(0 0 0 / 36%),
      0 0 0 3px color-mix(in oklab, var(--ring) 20%, transparent) !important;
  }

  [data-rehype-pretty-code-figure] {
    background-color: var(--color-code);
    color: var(--color-code-foreground);
    border-radius: var(--radius-xl);
    border-width: 0px;
    border-color: var(--border);
    margin-top: calc(var(--spacing) * 6);
    overflow: hidden;
    font-size: var(--text-sm);
    outline: none;
    position: relative;
    @apply -mx-1 md:-mx-1;

    &:has([data-rehype-pretty-code-title]) [data-slot="copy-button"] {
      top: calc(var(--spacing) * 1.5) !important;
    }
  }

  [data-rehype-pretty-code-title] {
    border-bottom: color-mix(in oklab, var(--border) 30%, transparent);
    border-bottom-width: 1px;
    border-bottom-style: solid;
    padding-block: calc(var(--spacing) * 2.5);
    padding-inline: calc(var(--spacing) * 4);
    font-size: var(--text-sm);
    font-family: var(--font-mono);
    color: var(--color-code-foreground);
  }

  [data-line-numbers] {
    display: grid;
    min-width: 100%;
    white-space: pre;
    border: 0;
    background: transparent;
    padding: 0;
    counter-reset: line;
    box-decoration-break: clone;
  }

  [data-line-numbers] [data-line]::before {
    font-size: var(--text-sm);
    counter-increment: line;
    content: counter(line);
    display: inline-block;
    width: calc(var(--spacing) * 16);
    padding-right: calc(var(--spacing) * 6);
    text-align: right;
    color: var(--color-code-number);
    background-color: var(--color-code);
    position: sticky;
    left: 0;
  }

  [data-line-numbers] [data-highlighted-line][data-line]::before {
    background-color: var(--color-code-highlight);
  }

  [data-line] {
    padding-top: calc(var(--spacing) * 0.5);
    padding-bottom: calc(var(--spacing) * 0.5);
    min-height: calc(var(--spacing) * 1);
    width: 100%;
    display: inline-block;
  }

  /*
   * ```text composition trees use box-drawing characters; per-line padding makes
   * vertical connectors look broken. rehype-pretty-code sets `data-language` on
   * `pre`/`code` (not `language-*` classes). It also sets `code { display: grid }`,
   * which can add visible row separation — reset to a normal pre stack for text.
   */
  [data-rehype-pretty-code-figure] pre[data-language="text"] code,
  [data-rehype-pretty-code-figure] pre[data-language="plaintext"] code,
  [data-slot="docs"] pre[data-language="text"] code,
  [data-slot="docs"] pre[data-language="plaintext"] code {
    display: block !important;
    white-space: pre;
    line-height: 0.95;
    font-family:
      ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
      "Courier New", monospace;
    font-variant-ligatures: none;
  }

  [data-rehype-pretty-code-figure] pre[data-language="text"] [data-line],
  [data-rehype-pretty-code-figure] pre[data-language="plaintext"] [data-line],
  [data-rehype-pretty-code-figure] code[data-language="text"] [data-line],
  [data-rehype-pretty-code-figure] code[data-language="plaintext"] [data-line],
  [data-slot="docs"] pre[data-language="text"] [data-line],
  [data-slot="docs"] pre[data-language="plaintext"] [data-line] {
    padding-top: 0;
    padding-bottom: 0;
    min-height: unset;
    line-height: 0.95;
    display: block;
  }

  [data-line] span {
    color: var(--shiki-light);

    @variant dark {
      color: var(--shiki-dark) !important;
    }
  }

  [data-highlighted-line],
  [data-highlighted-chars] {
    position: relative;
    background-color: var(--color-code-highlight);
  }

  [data-highlighted-line] {
    &:after {
      position: absolute;
      top: 0;
      left: 0;
      width: 2px;
      height: 100%;
      content: "";
      background-color: color-mix(
        in oklab,
        var(--muted-foreground) 50%,
        transparent
      );
    }
  }

  [data-highlighted-chars] {
    border-radius: var(--radius-sm);
    padding-inline: 0.3rem;
    padding-block: 0.1rem;
    font-family: var(--font-mono);
    font-size: 0.8rem;
  }
}

@layer components {
  .dialog-ring {
    @apply rounded-xl border-none bg-clip-padding shadow-2xl ring-4 ring-neutral-200/80 dark:bg-neutral-900 dark:ring-neutral-800;
  }
}

@theme inline {
  @keyframes accordion-down {
    from {
      height: 0;
    }
    to {
      height: var(
        --radix-accordion-content-height,
        var(--accordion-panel-height, auto)
      );
    }
  }

  @keyframes accordion-up {
    from {
      height: var(
        --radix-accordion-content-height,
        var(--accordion-panel-height, auto)
      );
    }
    to {
      height: 0;
    }
  }
}

/* Custom variants */
@custom-variant data-open {
  &:where([data-state="open"]),
  &:where([data-open]:not([data-open="false"])) {
    @slot;
  }
}

@custom-variant data-closed {
  &:where([data-state="closed"]),
  &:where([data-closed]:not([data-closed="false"])) {
    @slot;
  }
}

@custom-variant data-checked {
  &:where([data-state="checked"]),
  &:where([data-checked]:not([data-checked="false"])) {
    @slot;
  }
}

@custom-variant data-unchecked {
  &:where([data-state="unchecked"]),
  &:where([data-unchecked]:not([data-unchecked="false"])) {
    @slot;
  }
}

@custom-variant data-selected {
  &:where([data-selected="true"]) {
    @slot;
  }
}

@custom-variant data-disabled {
  &:where([data-disabled="true"]),
  &:where([data-disabled]:not([data-disabled="false"])) {
    @slot;
  }
}

@custom-variant data-active {
  &:where([data-state="active"]),
  &:where([data-active]:not([data-active="false"])) {
    @slot;
  }
}

@custom-variant data-horizontal {
  &:where([data-orientation="horizontal"]) {
    @slot;
  }
}

@custom-variant data-vertical {
  &:where([data-orientation="vertical"]) {
    @slot;
  }
}

@utility no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;

  &::-webkit-scrollbar {
    display: none;
  }
}
THIRD-PARTY-LICENSES.txt실행 안내·자료
파일 저장

react 19.2.3 — LICENSE

MIT License

Copyright (c) Meta Platforms, Inc. and affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

lodash 4.17.21 — LICENSE

Copyright OpenJS Foundation and other contributors <https://openjsf.org/>

Based on Underscore.js, copyright Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>

This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/lodash/lodash

The following license applies to all parts of this software except as
documented below:

====

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

====

Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code displayed within the prose of the
documentation.

CC0: http://creativecommons.org/publicdomain/zero/1.0/

====

Files located in the node_modules and vendor directories are externally
maintained libraries used by this software which have their own
licenses; we recommend you read them, as their terms may differ from the
terms above.


========================================================================

@glideapps/glide-data-grid 6.0.4-alpha24 — LICENSE

MIT License

Copyright (c) 2021 typeguard, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@emotion/memoize 0.9.0 — LICENSE

MIT License

Copyright (c) Emotion team and other contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@emotion/is-prop-valid 1.4.0 — LICENSE

MIT License

Copyright (c) Emotion team and other contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@linaria/core 6.3.0 — LICENSE

MIT License

Copyright (c) 2017 Callstack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@linaria/react 6.3.0 — LICENSE

MIT License

Copyright (c) 2017 Callstack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

prop-types 15.8.1 — LICENSE

MIT License

Copyright (c) 2013-present, Facebook, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

react-easy-swipe 0.0.21 — MIT notice from the official repository at a620dfd858b725190dd631199c7536e519a5140f; repository package version 0.0.23

MIT License

Copyright (c) 2016 Leandro Lemos

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

classnames 2.5.1 — LICENSE

The MIT License (MIT)

Copyright (c) 2018 Jed Watson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

react-responsive-carousel 3.2.23 — LICENSE.md

MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

marked 16.4.2 — LICENSE.md

# License information

## Contribution License Agreement

If you contribute code to this project, you are implicitly allowing your code
to be distributed under the MIT license. You are also implicitly verifying that
all code is your original work. `</legalese>`

## Marked

Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/)
Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

## Markdown

Copyright © 2004, John Gruber
http://daringfireball.net/
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.


========================================================================

canvas-hypertxt 1.0.3 — LICENSE

MIT License

Copyright (c) 2022 Glide

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

react-dom 19.2.3 — LICENSE

MIT License

Copyright (c) Meta Platforms, Inc. and affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

react-number-format 5.4.5 — LICENSE

MIT License

Copyright (c) 2020-present Sudhanshu Yadav

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@pierre/diffs 1.2.7 — LICENSE.md

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1.  Definitions.

    "License" shall mean the terms and conditions for use, reproduction, and
    distribution as defined by Sections 1 through 9 of this document.

    "Licensor" shall mean the copyright owner or entity authorized by the
    copyright owner that is granting the License.

    "Legal Entity" shall mean the union of the acting entity and all other
    entities that control, are controlled by, or are under common control with
    that entity. For the purposes of this definition, "control" means (i) the
    power, direct or indirect, to cause the direction or management of such
    entity, whether by contract or otherwise, or (ii) ownership of fifty percent
    (50%) or more of the outstanding shares, or (iii) beneficial ownership of
    such entity.

    "You" (or "Your") shall mean an individual or Legal Entity exercising
    permissions granted by this License.

    "Source" form shall mean the preferred form for making modifications,
    including but not limited to software source code, documentation source, and
    configuration files.

    "Object" form shall mean any form resulting from mechanical transformation
    or translation of a Source form, including but not limited to compiled
    object code, generated documentation, and conversions to other media types.

    "Work" shall mean the work of authorship, whether in Source or Object form,
    made available under the License, as indicated by a copyright notice that is
    included in or attached to the work (an example is provided in the Appendix
    below).

    "Derivative Works" shall mean any work, whether in Source or Object form,
    that is based on (or derived from) the Work and for which the editorial
    revisions, annotations, elaborations, or other modifications represent, as a
    whole, an original work of authorship. For the purposes of this License,
    Derivative Works shall not include works that remain separable from, or
    merely link (or bind by name) to the interfaces of, the Work and Derivative
    Works thereof.

    "Contribution" shall mean any work of authorship, including the original
    version of the Work and any modifications or additions to that Work or
    Derivative Works thereof, that is intentionally submitted to Licensor for
    inclusion in the Work by the copyright owner or by an individual or Legal
    Entity authorized to submit on behalf of the copyright owner. For the
    purposes of this definition, "submitted" means any form of electronic,
    verbal, or written communication sent to the Licensor or its
    representatives, including but not limited to communication on electronic
    mailing lists, source code control systems, and issue tracking systems that
    are managed by, or on behalf of, the Licensor for the purpose of discussing
    and improving the Work, but excluding communication that is conspicuously
    marked or otherwise designated in writing by the copyright owner as "Not a
    Contribution."

    "Contributor" shall mean Licensor and any individual or Legal Entity on
    behalf of whom a Contribution has been received by Licensor and subsequently
    incorporated within the Work.

2.  Grant of Copyright License. Subject to the terms and conditions of this
    License, each Contributor hereby grants to You a perpetual, worldwide,
    non-exclusive, no-charge, royalty-free, irrevocable copyright license to
    reproduce, prepare Derivative Works of, publicly display, publicly perform,
    sublicense, and distribute the Work and such Derivative Works in Source or
    Object form.

3.  Grant of Patent License. Subject to the terms and conditions of this
    License, each Contributor hereby grants to You a perpetual, worldwide,
    non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
    this section) patent license to make, have made, use, offer to sell, sell,
    import, and otherwise transfer the Work, where such license applies only to
    those patent claims licensable by such Contributor that are necessarily
    infringed by their Contribution(s) alone or by combination of their
    Contribution(s) with the Work to which such Contribution(s) was submitted.
    If You institute patent litigation against any entity (including a
    cross-claim or counterclaim in a lawsuit) alleging that the Work or a
    Contribution incorporated within the Work constitutes direct or contributory
    patent infringement, then any patent licenses granted to You under this
    License for that Work shall terminate as of the date such litigation is
    filed.

4.  Redistribution. You may reproduce and distribute copies of the Work or
    Derivative Works thereof in any medium, with or without modifications, and
    in Source or Object form, provided that You meet the following conditions:

    (a) You must give any other recipients of the Work or Derivative Works a
    copy of this License; and

    (b) You must cause any modified files to carry prominent notices stating
    that You changed the files; and

    (c) You must retain, in the Source form of any Derivative Works that You
    distribute, all copyright, patent, trademark, and attribution notices from
    the Source form of the Work, excluding those notices that do not pertain to
    any part of the Derivative Works; and

    (d) If the Work includes a "NOTICE" text file as part of its distribution,
    then any Derivative Works that You distribute must include a readable copy
    of the attribution notices contained within such NOTICE file, excluding
    those notices that do not pertain to any part of the Derivative Works, in at
    least one of the following places: within a NOTICE text file distributed as
    part of the Derivative Works; within the Source form or documentation, if
    provided along with the Derivative Works; or, within a display generated by
    the Derivative Works, if and wherever such third-party notices normally
    appear. The contents of the NOTICE file are for informational purposes only
    and do not modify the License. You may add Your own attribution notices
    within Derivative Works that You distribute, alongside or as an addendum to
    the NOTICE text from the Work, provided that such additional attribution
    notices cannot be construed as modifying the License.

    You may add Your own copyright statement to Your modifications and may
    provide additional or different license terms and conditions for use,
    reproduction, or distribution of Your modifications, or for any such
    Derivative Works as a whole, provided Your use, reproduction, and
    distribution of the Work otherwise complies with the conditions stated in
    this License.

5.  Submission of Contributions. Unless You explicitly state otherwise, any
    Contribution intentionally submitted for inclusion in the Work by You to the
    Licensor shall be under the terms and conditions of this License, without
    any additional terms or conditions. Notwithstanding the above, nothing
    herein shall supersede or modify the terms of any separate license agreement
    you may have executed with Licensor regarding such Contributions.

6.  Trademarks. This License does not grant permission to use the trade names,
    trademarks, service marks, or product names of the Licensor, except as
    required for reasonable and customary use in describing the origin of the
    Work and reproducing the content of the NOTICE file.

7.  Disclaimer of Warranty. Unless required by applicable law or agreed to in
    writing, Licensor provides the Work (and each Contributor provides its
    Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied, including, without limitation, any
    warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or
    FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining
    the appropriateness of using or redistributing the Work and assume any risks
    associated with Your exercise of permissions under this License.

8.  Limitation of Liability. In no event and under no legal theory, whether in
    tort (including negligence), contract, or otherwise, unless required by
    applicable law (such as deliberate and grossly negligent acts) or agreed to
    in writing, shall any Contributor be liable to You for damages, including
    any direct, indirect, special, incidental, or consequential damages of any
    character arising as a result of this License or out of the use or inability
    to use the Work (including but not limited to damages for loss of goodwill,
    work stoppage, computer failure or malfunction, or any and all other
    commercial damages or losses), even if such Contributor has been advised of
    the possibility of such damages.

9.  Accepting Warranty or Additional Liability. While redistributing the Work or
    Derivative Works thereof, You may choose to offer, and charge a fee for,
    acceptance of support, warranty, indemnity, or other liability obligations
    and/or rights consistent with this License. However, in accepting such
    obligations, You may act only on Your own behalf and on Your sole
    responsibility, not on behalf of any other Contributor, and only if You
    agree to indemnify, defend, and hold each Contributor harmless for any
    liability incurred by, or claims asserted against, such Contributor by
    reason of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

Copyright 2025 Pierre Computer Company

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at

       http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.


========================================================================

html-void-elements 3.0.0 — license

(The MIT License)

Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

property-information 7.2.0 — license

(The MIT License)

Copyright (c) Titus Wormer <mailto:tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

zwitch 2.0.4 — license

(The MIT License)

Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

stringify-entities 4.0.4 — license

(The MIT License)

Copyright (c) 2015 Titus Wormer <mailto:tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

character-entities-legacy 3.0.0 — license

(The MIT License)

Copyright (c) 2015 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

character-entities-html4 2.1.0 — license

(The MIT License)

Copyright (c) 2015 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

hast-util-to-html 9.0.5 — license

(The MIT License)

Copyright (c) Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

ccount 2.0.1 — license

(The MIT License)

Copyright (c) 2015 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

comma-separated-tokens 2.0.3 — license

(The MIT License)

Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

space-separated-tokens 2.0.2 — license

(The MIT License)

Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

hast-util-whitespace 3.0.0 — license

(The MIT License)

Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

@shikijs/types 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@shikijs/vscode-textmate 10.0.2 — LICENSE.md

The MIT License (MIT)

Copyright (c) Microsoft Corporation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@shikijs/core 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@shikijs/langs 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

shiki 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@shikijs/themes 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@shikijs/engine-oniguruma 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

oniguruma-parser 0.12.2 — LICENSE

MIT License

Copyright (c) 2025-2026 Steven Levithan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

regex 6.1.0 — LICENSE

MIT License

Copyright (c) 2025 Steven Levithan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

regex-utilities 2.3.0 — LICENSE

MIT License

Copyright (c) 2024 Steven Levithan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

regex-recursion 6.0.2 — LICENSE

MIT License

Copyright (c) 2025 Steven Levithan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

oniguruma-to-es 4.3.6 — LICENSE

MIT License

Copyright (c) 2024-2026 Steven Levithan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@shikijs/engine-javascript 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@pierre/theme 1.0.3 — LICENSE

MIT License

Copyright (c) 2026 The Pierre Computer Company

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@shikijs/transformers 3.23.0 — LICENSE

MIT License

Copyright (c) 2021 Pine Wu
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

diff 8.0.3 — LICENSE

BSD 3-Clause License

Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


========================================================================

lru_map 0.4.1 — exact license section in README.md

# MIT license

Copyright (c) 2010-2016 Rasmus Andersson <https://rsms.me/>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


========================================================================

clsx 2.1.1 — license

MIT License

Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

tailwind-merge 3.3.1 — LICENSE.md

MIT License

Copyright (c) 2021 Dany Castillo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@base-ui/utils 0.2.8 — LICENSE

The MIT License (MIT)

Copyright (c) 2019 Material-UI SAS

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@base-ui/react 1.4.1 — LICENSE

The MIT License (MIT)

Copyright (c) 2019 Material-UI SAS

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

class-variance-authority 0.7.1 — LICENSE

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2022 Joe Bell

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


========================================================================

@hugeicons/core-free-icons 4.2.0 — MIT notice from the official repository at ed8816e7f9f918df3c31c6abe2d5143d758851bd; repository package version publisher-wide free-icons notice

MIT License

Copyright (c) 2025 Hugeicons

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@hugeicons/react 1.1.6 — LICENSE.md

MIT License

Copyright (c) 2025 Hugeicons

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@floating-ui/utils 0.2.12 — LICENSE

MIT License

Copyright (c) 2021-present Floating UI contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

use-sync-external-store 1.7.0 — LICENSE

MIT License

Copyright (c) Meta Platforms, Inc. and affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@floating-ui/core 1.8.0 — LICENSE

MIT License

Copyright (c) 2021-present Floating UI contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

@floating-ui/dom 1.8.0 — LICENSE

MIT License

Copyright (c) 2021-present Floating UI contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

@floating-ui/react-dom 2.1.9 — LICENSE

MIT License

Copyright (c) 2021-present Floating UI contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

reselect 5.3.0 — LICENSE

The MIT License (MIT)

Copyright (c) 2015-2018 Reselect Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

scheduler 0.27.0 — LICENSE

MIT License

Copyright (c) Meta Platforms, Inc. and affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

tailwindcss 4.1.13 — LICENSE

MIT License

Copyright (c) Tailwind Labs, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

tw-animate-css 1.3.8 — LICENSE

MIT License

Copyright (c) 2025 Wombosvideo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
provenance.json실행 안내·자료
파일 저장

{
  "id": "21st-8be0498b064f",
  "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
  "demoIdentity": {
    "file": "baseline-example.tsx",
    "export": "default",
    "source": "exact-public-demo"
  },
  "fidelity": {
    "preserved": "21st 공개 discovery의 고유 데모 ID와 미리보기 이미지가 가리키는 원래 데모 바이트 전체",
    "dependency_revision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
    "observed_differences": [
      "현재 작성자 커밋의 실제 HumanReviewPanel을 원래 680px 예제 wrapper에 연결한다. PDF viewer나 다른 block을 임의 추가하지 않는다."
    ],
    "mapping": []
  },
  "acquisitionLimitations": [
    "CDN 데모는 SHA256으로, 작성자 원본과 의존 파일은 Git 커밋 및 blob SHA로 고정했다.",
    "현재 작성자 revision의 의존 파일이 과거 21st 내부 구현과 바이트 단위로 같다는 주장은 하지 않는다.",
    "다운로드한 코드를 실행하지 않았으며 브라우저·상호작용 검증은 별도 단계다."
  ],
  "files": [
    {
      "file": "baseline-example.tsx",
      "kind": "implementation",
      "sha256": "e1676438fcd022195ad2acfbfbb3fa591055ebe1ab6ac9be4825ee17e7836bfc",
      "sourceRevision": "sha256:e1676438fcd022195ad2acfbfbb3fa591055ebe1ab6ac9be4825ee17e7836bfc",
      "license": "MIT"
    },
    {
      "file": "LICENSE",
      "kind": "license",
      "sha256": "9f05510549c7c2ad283764dfe0e527278e5fb075fb750a9f1dd148f3763269e5",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/extend/bounding-box-citations.tsx",
      "kind": "implementation-dependency",
      "sha256": "ad7ded7f8d5e149efc687c6839fc0262261acd0932d5cb21210811adaf82a057",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/lib/registry-icon-props.ts",
      "kind": "implementation-dependency",
      "sha256": "a331e72bbe23dbd4bac6827dff2a2867d31638fa74daf69ea4c1f41873a686d6",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/lib/utils.ts",
      "kind": "implementation-dependency",
      "sha256": "b80bd7ef9714af80100bd9a5c56f6d415cdc06af9083cd5b218bc6f92d622596",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/ui/button.tsx",
      "kind": "implementation-dependency",
      "sha256": "cfa1ad200f7f0e7757c34b8df42726298330a206ed7a2f3a7c16162ec96227a8",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/ui/input.tsx",
      "kind": "implementation-dependency",
      "sha256": "1b1d59d2781557568968573d758c226bb385e36d262394a138315e909113ab6f",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/ui/scroll-area.tsx",
      "kind": "implementation-dependency",
      "sha256": "4113dd6f63b70ea3681aa5e6837773b7ab1a74bb73fea2a7ace380b503e48187",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/ui/tabs.tsx",
      "kind": "implementation-dependency",
      "sha256": "fe4002f88952cd2b87e77b452ffa0d801d1418acdfb871268c2fcacf8b6be23f",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/ui/tooltip.tsx",
      "kind": "implementation-dependency",
      "sha256": "45f74cdca29c400714c6e03e99ca5754fa2fc56557153404d1fbba77c6def0d7",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/icon-placeholder.tsx",
      "kind": "implementation-dependency",
      "sha256": "4592673a15f005b5682264a2232b06009220f159edbae7e047fc10900d5ec070",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/lib/config.ts",
      "kind": "implementation-dependency",
      "sha256": "8ae8af0993afa15d423e7032dc4c6e4f492549dc1caf9590e40e8521edf80dab",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/components/ui/spinner.tsx",
      "kind": "implementation-dependency",
      "sha256": "eef80891884720b054d549515ff541f3e36884a6da430c5df7aa4397391f4790",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/registry/new-york-v4/ui/input.tsx",
      "kind": "implementation-dependency",
      "sha256": "bc51fa452f920b7e19c396fe4d8ea5bb53670d93d9026a206542027e3e4f9f9d",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/registry/new-york-v4/ui/tabs.tsx",
      "kind": "implementation-dependency",
      "sha256": "a32b095106026b3e105fb5276cf73872900cc1933e61fa5852d722b509083352",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/registry/new-york-v4/ui/tooltip.tsx",
      "kind": "implementation-dependency",
      "sha256": "b7c98aab7b3162e551b29d0f82c329e311ae4a8d9590956e4ba0d15d95d429f8",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/registry/new-york-v4/ui/spinner.tsx",
      "kind": "implementation-dependency",
      "sha256": "e4794a099d77b3f220e0998b6777036175e186e238fd95600697d0fd89052795",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/app/globals.css",
      "kind": "theme-source",
      "sha256": "f929d2c1de31312f6fc53bd9a7fff9f6a3feff46d4d5fd65805fea2deddbc759",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/app/shadcn-tailwind.css",
      "kind": "theme-source",
      "sha256": "146941ac3ff65496fdf1cb306e697255328e4dadc7c105d66e36bb031b00f6d6",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    },
    {
      "file": "author/apps/v4/package.json",
      "kind": "dependency-manifest",
      "sha256": "867c41d3eaf150a51c8294e75f7be09561f54723c6584665641203fdc21f3b1c",
      "sourceRevision": "a0ddcecd7bb3c92458b148cb0d34cbd5263191d4",
      "license": "MIT"
    }
  ],
  "importMap": {
    "@/components/ui/bounding-box-citations": "author/apps/v4/components/extend/bounding-box-citations.tsx",
    "@/lib/registry-icon-props": "author/apps/v4/lib/registry-icon-props.ts",
    "@/lib/utils": "author/apps/v4/lib/utils.ts",
    "@/components/ui/button": "author/apps/v4/components/ui/button.tsx",
    "@/components/ui/input": "author/apps/v4/components/ui/input.tsx",
    "@/components/ui/scroll-area": "author/apps/v4/components/ui/scroll-area.tsx",
    "@/components/ui/tabs": "author/apps/v4/components/ui/tabs.tsx",
    "@/components/ui/tooltip": "author/apps/v4/components/ui/tooltip.tsx",
    "@/components/icon-placeholder": "author/apps/v4/components/icon-placeholder.tsx",
    "@/lib/config": "author/apps/v4/lib/config.ts",
    "@/components/ui/spinner": "author/apps/v4/components/ui/spinner.tsx",
    "@/registry/new-york-v4/ui/input": "author/apps/v4/registry/new-york-v4/ui/input.tsx",
    "@/registry/new-york-v4/ui/tabs": "author/apps/v4/registry/new-york-v4/ui/tabs.tsx",
    "@/registry/new-york-v4/ui/tooltip": "author/apps/v4/registry/new-york-v4/ui/tooltip.tsx",
    "@/registry/new-york-v4/ui/spinner": "author/apps/v4/registry/new-york-v4/ui/spinner.tsx"
  },
  "declaredDependencies": {
    "@base-ui/react": "^1.4.1",
    "@glideapps/glide-data-grid": "6.0.4-alpha24",
    "@hugeicons/core-free-icons": "^4.2.0",
    "@hugeicons/react": "^1.1.6",
    "@pierre/diffs": "^1.2.7",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "tailwind-merge": "^3.3.1"
  },
  "runtimeDependencies": {
    "react": "19.2.3",
    "lodash": "4.17.21",
    "@glideapps/glide-data-grid": "6.0.4-alpha24",
    "@emotion/memoize": "0.9.0",
    "@emotion/is-prop-valid": "1.4.0",
    "@linaria/core": "6.3.0",
    "@linaria/react": "6.3.0",
    "prop-types": "15.8.1",
    "react-easy-swipe": "0.0.21",
    "classnames": "2.5.1",
    "react-responsive-carousel": "3.2.23",
    "marked": "16.4.2",
    "canvas-hypertxt": "1.0.3",
    "react-dom": "19.2.3",
    "react-number-format": "5.4.5",
    "@pierre/diffs": "1.2.7",
    "html-void-elements": "3.0.0",
    "property-information": "7.2.0",
    "zwitch": "2.0.4",
    "stringify-entities": "4.0.4",
    "character-entities-legacy": "3.0.0",
    "character-entities-html4": "2.1.0",
    "hast-util-to-html": "9.0.5",
    "ccount": "2.0.1",
    "comma-separated-tokens": "2.0.3",
    "space-separated-tokens": "2.0.2",
    "hast-util-whitespace": "3.0.0",
    "@shikijs/types": "3.23.0",
    "@shikijs/vscode-textmate": "10.0.2",
    "@shikijs/core": "3.23.0",
    "@shikijs/langs": "3.23.0",
    "shiki": "3.23.0",
    "@shikijs/themes": "3.23.0",
    "@shikijs/engine-oniguruma": "3.23.0",
    "oniguruma-parser": "0.12.2",
    "regex": "6.1.0",
    "regex-utilities": "2.3.0",
    "regex-recursion": "6.0.2",
    "oniguruma-to-es": "4.3.6",
    "@shikijs/engine-javascript": "3.23.0",
    "@pierre/theme": "1.0.3",
    "@shikijs/transformers": "3.23.0",
    "diff": "8.0.3",
    "lru_map": "0.4.1",
    "clsx": "2.1.1",
    "tailwind-merge": "3.3.1",
    "@base-ui/utils": "0.2.8",
    "@base-ui/react": "1.4.1",
    "class-variance-authority": "0.7.1",
    "@hugeicons/core-free-icons": "4.2.0",
    "@hugeicons/react": "1.1.6",
    "@floating-ui/utils": "0.2.12",
    "use-sync-external-store": "1.7.0",
    "@floating-ui/core": "1.8.0",
    "@floating-ui/dom": "1.8.0",
    "@floating-ui/react-dom": "2.1.9",
    "reselect": "5.3.0",
    "scheduler": "0.27.0",
    "tailwindcss": "4.1.13",
    "tw-animate-css": "1.3.8"
  },
  "assets": [],
  "assetAdaptations": [],
  "adaptations": [
    "The exact public baseline renders HumanReviewPanel with !h-[680px]. It does not instantiate a PDF viewer or load a document.",
    "The pinned author review fields, original editors, default Form tab, and JSON comparison are retained. The host supplies an isolated local runtime and selected original theme CSS.",
    "Documentation-only theme imports are omitted; the original component implementation and local import closure are unchanged."
  ],
  "runtime_verified": false
}
README.md실행 안내·자료
파일 저장

# Human Review Panel · Extend UI

The acquired original files are unchanged. Source revision: a0ddcecd7bb3c92458b148cb0d34cbd5263191d4. Each exact file hash and any demo content revision is recorded in provenance.json. Keep all included license notices.

- The exact public baseline renders HumanReviewPanel with !h-[680px]. It does not instantiate a PDF viewer or load a document.
- The pinned author review fields, original editors, default Form tab, and JSON comparison are retained. The host supplies an isolated local runtime and selected original theme CSS.
- Documentation-only theme imports are omitted; the original component implementation and local import closure are unchanged.
- Host spacing and fallback tokens come from runtime/author-styles.css. runtime/applied-theme.css contains the selected original author theme and overrides fallback tokens.

Open the standalone HTML to run with all JavaScript and CSS bundled locally. ?theme=light and ?theme=dark select the host theme. The parent gallery must use an opaque allow-scripts sandbox. No external requests are required.

For source reuse, start with Usage.tsx and the unchanged baseline demonstration. Resolve @/ imports to their included matching author files and install the exact package versions in provenance.json. The host mount and styles are supplied for reference.

Build success and the ready message do not prove runtime or accessibility behavior. Browser validation is recorded separately.