{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"item","type":"registry:component","title":"Item","description":"The generic list row: media, title, description, meta and trailing actions; becomes a link or button when interactive.","categories":["display"],"dependencies":["@radix-ui/react-direction@1.1.4","clsx@^2.1.1","tailwind-merge@^3.7.0"],"registryDependencies":["https://beamline.io/registry/look-prism.json"],"files":[{"path":"beamline/components/item/defaults.ts","type":"registry:file","target":"src/ui/beamline/components/item/defaults.ts","content":"/** Pure allowed presentation defaults for Item; no component or runtime imports. */\nexport interface ItemDefaults {\n  /** Shared visual weight; ghost by default. @deprecated Use ghost for plain, or secondary for card. */\n  variant?: \"ghost\" | \"outline\" | \"secondary\" | \"plain\" | \"card\";\n  /** Shared size setting; omit to inherit the surrounding scope or the component default. */\n  size?: \"sm\" | \"md\";\n}\n"},{"path":"beamline/components/item/item.module.css","type":"registry:file","target":"src/ui/beamline/components/item/item.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* Item: one row. Plain rows are bare until pointed at; outline draws a quiet hairline; card wears the surface role.\n     Selected (O1 §6): accent tint, a 2px accent-ink bar on the inline-start side, and every line of text in ink, so the\n     state never rests on colour alone and the text keeps 4.5:1 on the tint in every look. */\n  .root {\n    position: relative;\n    display: flex;\n    align-items: center;\n    gap: var(--pui-space-2);\n    min-width: 0;\n    padding-inline: var(--pui-space-3);\n    border-radius: var(--pui-radius-control);\n    /* the corner an icon tile in the row's corner zone must take (O1 nested radius = outer − inset) */\n    --_item-nest: calc(var(--pui-radius-control) - var(--_pb, var(--pui-space-2-5)));\n    transition:\n      background-color var(--pui-motion-out) var(--pui-ease-default),\n      var(--m-transition);\n  }\n  .outline {\n    --_item-nest: calc(var(--pui-radius-card) - var(--_pb, var(--pui-space-2-5)));\n    border-radius: var(--pui-radius-card);\n    box-shadow: var(--pui-shadow-hairline);\n  }\n  .card {\n    --_item-nest: calc(var(--pui-radius-card) - var(--_pb, var(--pui-space-2-5)));\n    border-radius: var(--pui-radius-card);\n  }\n  .root[data-interactive]:hover {\n    background-color: var(--pui-hover);\n    transition-duration: 0ms;\n  }\n  /* a press steps one more rung at once */\n  .root[data-interactive]:has(> .main:active) {\n    background-color: var(--pui-hover-2);\n    transition-duration: 0ms;\n  }\n  .root[data-state=\"selected\"],\n  .root[data-state=\"selected\"]:hover {\n    background-color: var(--pui-accent-tint);\n  }\n  /* the selection bar is always there and grows from its middle when the row is chosen (a transition, so a row that\n     starts selected simply is) */\n  .root::before {\n    content: \"\";\n    position: absolute;\n    inset-block: 8px;\n    inset-inline-start: 0;\n    width: 2px;\n    border-radius: var(--pui-radius-pill);\n    background: var(--pui-accent-ink);\n    opacity: 0;\n    scale: 1 0.3;\n    transition:\n      opacity var(--pui-motion-out) var(--pui-ease-default),\n      scale var(--pui-motion-out) var(--pui-ease-default);\n    pointer-events: none;\n  }\n  .root[data-state=\"selected\"]::before {\n    opacity: 1;\n    scale: 1 1;\n    transition-duration: var(--pui-duration-fast);\n  }\n  .main {\n    display: flex;\n    flex: 1;\n    min-width: 0;\n    align-items: center;\n    gap: var(--pui-space-3);\n    padding-block: var(--pui-space-2-5);\n    border-radius: var(--pui-radius-control);\n    color: inherit;\n    text-align: start;\n    text-decoration: none;\n    outline-offset: 2px;\n  }\n  .sm {\n    --_pb: var(--pui-space-1-5);\n  }\n  .sm .main {\n    padding-block: var(--pui-space-1-5);\n  }\n  .media {\n    display: grid;\n    flex: none;\n    place-items: center;\n  }\n  .text {\n    display: flex;\n    flex: 1;\n    flex-direction: column;\n    min-width: 0;\n  }\n  .title {\n    overflow: hidden;\n    color: var(--pui-ink);\n    font-size: var(--pui-text-base);\n    font-weight: var(--pui-weight-medium);\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .sm .title {\n    font-size: var(--pui-text-sm);\n  }\n  .description {\n    overflow: hidden;\n    color: var(--pui-ink-3);\n    font-size: var(--pui-text-xs);\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .meta {\n    flex: none;\n    color: var(--pui-ink-3);\n    font-size: var(--pui-text-xs);\n    font-variant-numeric: tabular-nums;\n    unicode-bidi: isolate;\n  }\n  .root[data-state=\"selected\"] :is(.description, .meta) {\n    color: var(--pui-ink);\n  }\n  .actions {\n    display: flex;\n    flex: none;\n    align-items: center;\n    gap: var(--pui-space-1);\n  }\n  /* a narrow room (below 20rem, measured) joins what a wide one separates: the meta moves onto the description line,\n     so the title keeps the whole width (a row with no description keeps its meta beside the title) */\n  .root[data-narrow] .main:has(.description) {\n    display: grid;\n    grid-template-columns: auto minmax(0, 1fr) auto;\n    grid-template-areas:\n      \"media title title\"\n      \"media description meta\";\n    column-gap: var(--pui-space-3);\n    align-items: center;\n  }\n  .root[data-narrow] .main:has(.description) .text {\n    display: contents;\n  }\n  .root[data-narrow] .main:has(.description) .media {\n    grid-area: media;\n  }\n  .root[data-narrow] .main:has(.description) .title {\n    grid-area: title;\n  }\n  .root[data-narrow] .main:has(.description) .description {\n    grid-area: description;\n  }\n  .root[data-narrow] .main:has(.description) .meta {\n    grid-area: meta;\n    align-self: baseline;\n    font-size: var(--pui-text-2xs);\n  }\n  /* ItemIcon: a tinted tile behind an icon; its corner follows the look's control radius. */\n  .icon {\n    display: grid;\n    width: 32px;\n    height: 32px;\n    place-items: center;\n    /* its own control corner, or concentric with the row (never squarer than a chip, O1 §14.1) when the row's corner\n       is round enough to reach it; the 100000× term switches between the two without a conditional */\n    --_r: max(\n      var(--_item-nest, -1px),\n      var(--pui-radius-chip),\n      var(--pui-radius-control) - max(var(--_item-nest, -1px), 0px) * 100000\n    );\n    border-radius: var(--_r);\n    background: var(--_tile, var(--pui-hover-2));\n    color: var(--_glyph, var(--pui-ink-2));\n  }\n  .icon > svg {\n    width: 16px;\n    height: 16px;\n  }\n  .icon[data-tone=\"accent\"] {\n    --_tile: var(--pui-accent-tint);\n    --_glyph: var(--pui-accent-ink);\n  }\n  .icon[data-tone=\"green\"] {\n    --_tile: var(--pui-green-tint);\n    --_glyph: var(--pui-green);\n  }\n  .icon[data-tone=\"orange\"] {\n    --_tile: var(--pui-orange-tint);\n    --_glyph: var(--pui-orange);\n  }\n  .icon[data-tone=\"red\"] {\n    --_tile: var(--pui-red-tint);\n    --_glyph: var(--pui-red);\n  }\n  .icon[data-tone=\"blue\"] {\n    --_tile: var(--pui-blue-tint);\n    --_glyph: var(--pui-blue);\n  }\n  .icon[data-tone=\"violet\"] {\n    --_tile: var(--pui-violet-tint);\n    --_glyph: var(--pui-violet);\n  }\n}\n/* CSS-module composition only; visual rules stay in the component layer. */\n.card {\n  composes: m-surface from global;\n}\n"},{"path":"beamline/components/item/item.tsx","type":"registry:file","target":"src/ui/beamline/components/item/item.tsx","content":"\"use client\";\n\nimport { useDeprecatedProp } from \"../../hooks/use-deprecated-prop\";\n\nimport { useEffect, useRef, type CSSProperties, type ComponentProps, type RefObject } from \"react\";\n\nimport { useComponentDefaults } from \"../../runtime/appearance\";\nimport type { ItemDefaults } from \"./defaults\";\nexport type { ItemDefaults } from \"./defaults\";\n\nimport type { ReactNode, Ref } from \"react\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useWidthAs } from \"../../hooks/use-content-width\";\nimport { cn } from \"../../lib/utils\";\nimport { useControllable } from \"../../hooks/use-controllable\";\nimport styles from \"./item.module.css\";\n\nexport interface ItemClassNames {\n  /** Classes for the root part; no override by default. */\n  root?: string;\n  /** Classes for the main part; no override by default. */\n  main?: string;\n  /** Classes for the media part; no override by default. */\n  media?: string;\n  /** Classes for the text part; no override by default. */\n  text?: string;\n  /** Classes for the title part; no override by default. */\n  title?: string;\n  /** Classes for the description part; no override by default. */\n  description?: string;\n  /** Classes for the meta part; no override by default. */\n  meta?: string;\n  /** Classes for the actions part; no override by default. */\n  actions?: string;\n}\n\nexport interface ItemProps {\n  /** Heading shown above the content. Required. */\n  title: ReactNode;\n  /** Supporting text beneath the label or heading. Defaults to no supporting text. */\n  description?: ReactNode;\n  /** Icon tile, avatar or thumbnail on the start side. */\n  media?: ReactNode;\n  /** Right-aligned meta: time, count, status. */\n  meta?: ReactNode;\n  /** Trailing controls. Clicks inside do not trigger the row. They wait for attention (hover, focus in the row, the\n   *  row selected; always shown on touch) and stay while one of them is pressed or has its menu open. */\n  actions?: ReactNode;\n  /** Show the actions at rest instead of with attention. Default false. */\n  actionsAtRest?: boolean;\n  /** URL opened when the item is activated. Defaults to a non-link item. */\n  href?: string;\n  /** Called when an item is activated; no handler by default. */\n  onSelect?: () => void;\n  /** Controlled selected indication; omitted by default to use defaultSelected. */\n  selected?: boolean;\n  /** Initial selected indication when uncontrolled; defaults to false. */\n  defaultSelected?: boolean;\n  /** Reports selecting the row; no callback by default, and onSelect remains the activation event. */\n  onSelectedChange?: (selected: boolean) => void;\n  /** Visual weight; defaults to ghost. @deprecated The values plain and card alias ghost and secondary. */\n  variant?: \"ghost\" | \"outline\" | \"secondary\" | \"plain\" | \"card\";\n  /** Control size within the supported size steps. Defaults to \"md\". */\n  size?: \"sm\" | \"md\";\n  /** Classes on the documented visual root; no override by default. */\n  className?: string;\n  /** Inline styles on the documented visual root; no inline overrides by default. */\n  style?: CSSProperties;\n  /** Classes for the named visual parts; no part overrides by default. */\n  classNames?: ItemClassNames;\n  /** Receives the documented root element; no ref by default. */\n  ref?: Ref<HTMLDivElement>;\n}\n\n/** Watched by nothing: a row that has no fold to make measures nothing. */\nconst UNWATCHED: RefObject<HTMLDivElement | null> = { current: null };\n\nconst VARIANT: Record<NonNullable<ItemProps[\"variant\"]>, string | undefined> = {\n  ghost: undefined,\n  plain: undefined,\n  outline: styles.outline,\n  secondary: styles.card,\n  card: styles.card,\n};\n\n/**\n * The generic list row every list is built from: media, title, description, meta and actions. It becomes a link or a\n * button when given `href` / `onSelect`, keeping trailing actions separately focusable. It answers its room: below\n * 20rem the meta joins the description line so the title keeps the whole width; trailing actions wait for attention.\n */\nexport function Item({\n  title,\n  description,\n  media,\n  meta,\n  actions,\n  actionsAtRest = false,\n  href,\n  onSelect,\n  selected: selectedProp,\n  defaultSelected,\n  onSelectedChange,\n  variant: variantProp,\n  size: sizeProp,\n  className,\n  style,\n  classNames,\n  ref,\n}: ItemProps) {\n  const scopeDefaults = useComponentDefaults<ItemDefaults>(\"item\", [\"variant\", \"size\"]);\n  const requestedVariant = variantProp ?? scopeDefaults.variant ?? \"ghost\";\n  useDeprecatedProp(\"Item\", 'variant=\"plain\"', 'variant=\"ghost\"', requestedVariant === \"plain\");\n  useDeprecatedProp(\"Item\", 'variant=\"card\"', 'variant=\"secondary\"', requestedVariant === \"card\");\n  const variant = requestedVariant === \"ghost\" ? \"plain\" : requestedVariant === \"secondary\" ? \"card\" : requestedVariant;\n  const [selected, setSelected] = useControllable(selectedProp, defaultSelected ?? false);\n  const selectable = defaultSelected !== undefined || onSelectedChange !== undefined;\n  const choose = () => {\n    // an item nobody listens to stays a plain row: it keeps no selection of its own\n    if (selectable) setSelected(true);\n    onSelectedChange?.(true);\n    onSelect?.();\n  };\n  const size = sizeProp ?? scopeDefaults.size ?? \"md\";\n\n  const interactive = !!(href || onSelect || selectable);\n  // only a row with both a description and a meta has a fold to make, so only such a row measures its width (one\n  // observer, read when the width crosses 20rem); no containment, so a row can sit in a scrolling strip at its own width\n  const root = useRef<HTMLDivElement>(null);\n  const rootRef = useComposedRef(root, ref);\n  const narrow = useWidthAs(description && meta ? root : UNWATCHED, (w) => w < 320, false);\n  const tools = useRef<HTMLSpanElement>(null);\n  // the actions stay shown while one is pressed (a favourite that is on) or has its menu open; watched only for those\n  // attributes, so a still row costs nothing\n  const hasActions = !!actions && !actionsAtRest;\n  useEffect(() => {\n    const el = tools.current;\n    if (!el || !hasActions || typeof MutationObserver === \"undefined\") return;\n    const held = '[aria-pressed=\"true\"],[aria-expanded=\"true\"],[data-state=\"open\"]';\n    const update = () => el.toggleAttribute(\"data-shown\", !!el.querySelector(held));\n    update();\n    const watch = new MutationObserver(update);\n    watch.observe(el, {\n      subtree: true,\n      attributes: true,\n      attributeFilter: [\"aria-pressed\", \"aria-expanded\", \"data-state\"],\n    });\n    return () => watch.disconnect();\n  }, [hasActions]);\n  const body = (\n    <>\n      {media && (\n        <span data-slot=\"item-media\" className={cn(styles.media, classNames?.media)}>\n          {media}\n        </span>\n      )}\n      <span className={cn(styles.text, classNames?.text)} data-slot=\"item-text\">\n        <span data-slot=\"item-title\" className={cn(styles.title, classNames?.title)}>\n          {title}\n        </span>\n        {description && (\n          <span data-slot=\"item-description\" className={cn(styles.description, classNames?.description)}>\n            {description}\n          </span>\n        )}\n      </span>\n      {meta && (\n        <span data-slot=\"item-meta\" className={cn(styles.meta, classNames?.meta)}>\n          {meta}\n        </span>\n      )}\n    </>\n  );\n  return (\n    <div\n      ref={rootRef}\n      style={style}\n      data-slot=\"item\"\n      data-narrow={narrow || undefined}\n      data-variant={variant}\n      data-state={selected ? \"selected\" : undefined}\n      data-selected={selected || undefined}\n      data-interactive={interactive || undefined}\n      className={cn(\n        \"reveal-host\",\n        styles.root,\n        VARIANT[variant],\n        size === \"sm\" && styles.sm,\n        classNames?.root,\n        className,\n      )}\n    >\n      {href ? (\n        <a\n          href={href}\n          onClick={selectable ? choose : undefined}\n          className={cn(styles.main, classNames?.main)}\n          data-slot=\"item-main\"\n          aria-current={selected ? \"page\" : undefined}\n        >\n          {body}\n        </a>\n      ) : onSelect || selectable ? (\n        <button\n          type=\"button\"\n          onClick={choose}\n          aria-pressed={selected}\n          className={cn(styles.main, classNames?.main)}\n          data-slot=\"item-main\"\n        >\n          {body}\n        </button>\n      ) : (\n        <div className={cn(styles.main, classNames?.main)} data-slot=\"item-main\">\n          {body}\n        </div>\n      )}\n      {actions && (\n        <span\n          ref={tools}\n          data-slot=\"item-actions\"\n          className={cn(styles.actions, !actionsAtRest && \"reveal\", classNames?.actions)}\n        >\n          {actions}\n        </span>\n      )}\n    </div>\n  );\n}\n\nexport type ItemIconPalette = \"green\" | \"orange\" | \"red\" | \"blue\" | \"violet\";\nexport type ItemIconTone = \"neutral\" | \"accent\" | \"success\" | \"warning\" | \"danger\" | \"info\" | ItemIconPalette;\nconst ICON_TONES: Record<ItemIconTone, string> = {\n  neutral: \"neutral\",\n  accent: \"accent\",\n  success: \"green\",\n  warning: \"orange\",\n  danger: \"red\",\n  info: \"blue\",\n  green: \"green\",\n  orange: \"orange\",\n  red: \"red\",\n  blue: \"blue\",\n  violet: \"violet\",\n};\n\n/** A tinted tile behind an icon — the default media for items. Its corner follows the look's control radius. */\nexport function ItemIcon({\n  children,\n  tone = \"neutral\",\n  palette,\n  className,\n  ...props\n}: ComponentProps<\"span\"> & {\n  /** Semantic colour; defaults to neutral. @deprecated Named colours should use palette instead. */\n  tone?: ItemIconTone;\n  /** Explicit decorative colour overriding the semantic tone; omitted by default. */\n  palette?: ItemIconPalette;\n}) {\n  useDeprecatedProp(\n    \"ItemIcon\",\n    \"tone\",\n    \"a semantic tone or palette for a named colour\",\n    [\"green\", \"orange\", \"red\", \"blue\", \"violet\"].includes(tone),\n  );\n  return (\n    <span\n      {...props}\n      data-slot=\"item-icon\"\n      data-tone={palette ?? ICON_TONES[tone]}\n      className={cn(styles.icon, className)}\n    >\n      {children}\n    </span>\n  );\n}\n\nexport default Item;\n"},{"path":"beamline/hooks/use-composed-ref.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-composed-ref.ts","content":"\"use client\";\n\nimport { useCallback, type Ref, type RefObject } from \"react\";\n\n/** Keep a control's measurement/focus ref and the caller's ref, including React 19 callback cleanup. */\nexport function useComposedRef<T>(internal: RefObject<T | null>, external?: Ref<T>) {\n  return useCallback(\n    (node: T | null) => {\n      internal.current = node;\n      if (typeof external === \"function\") {\n        const cleanup = external(node);\n        if (typeof cleanup === \"function\")\n          return () => {\n            internal.current = null;\n            cleanup();\n          };\n      } else if (external) external.current = node;\n    },\n    [internal, external],\n  );\n}\n"},{"path":"beamline/hooks/use-content-width.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-content-width.ts","content":"\"use client\";\n\nimport { useLayoutEffect, useRef, useState, type RefObject } from \"react\";\n\nconst same = (width: number) => width;\n\n/**\n * The width of an element's content box, kept current with a ResizeObserver. It is first read in a layout effect, so\n * a layout chosen by width (a narrow mode, a chart's scale) is right in the first frame instead of jumping a frame\n * later. `fallback` stands in on the server and until the element exists; `key` re-measures when the element is\n * replaced (one that exists only in one view).\n */\nexport function useContentWidth(ref: RefObject<HTMLElement | null>, fallback: number, key?: unknown): number {\n  return useWidthAs(ref, same, fallback, key);\n}\n\n/**\n * The same width, kept as what a part needs from it (`(w) => w < 640` for a narrow mode): the part renders again only\n * when that changes, not at every pixel.\n */\nexport function useWidthAs<T>(\n  ref: RefObject<HTMLElement | null>,\n  read: (width: number) => T,\n  fallback: T,\n  key?: unknown,\n): T {\n  const [value, setValue] = useState(fallback);\n  const latest = useRef(read);\n  latest.current = read;\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    // the computed width is fractional and ignores transforms, like the observer's content box\n    const s = getComputedStyle(el);\n    const edges = [\"paddingLeft\", \"paddingRight\", \"borderLeftWidth\", \"borderRightWidth\"] as const;\n    const box = parseFloat(s.width);\n    const outer = Number.isFinite(box) ? box : el.offsetWidth;\n    const inner =\n      Number.isFinite(box) && s.boxSizing !== \"border-box\" ? 0 : edges.reduce((w, k) => w + parseFloat(s[k]), 0);\n    setValue(latest.current(outer - inner));\n    if (typeof ResizeObserver === \"undefined\") return;\n    const ro = new ResizeObserver(([e]) => setValue(latest.current(e.contentRect.width)));\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [ref, key]);\n  return value;\n}\n\n/**\n * The height of an element's content box, kept current like `useContentWidth`: for parts that lay out at whatever\n * height their parent gives them (a chart, a map or a book that fills its panel). Read in a layout effect, so the first\n * frame is right; with `enabled` false nothing is measured and the fallback stands.\n */\nexport function useContentHeight(\n  ref: RefObject<HTMLElement | null>,\n  fallback: number,\n  key?: unknown,\n  enabled = true,\n): number {\n  const [value, setValue] = useState(fallback);\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!enabled || !el) return;\n    const s = getComputedStyle(el);\n    setValue(el.clientHeight - (parseFloat(s.paddingTop) || 0) - (parseFloat(s.paddingBottom) || 0));\n    if (typeof ResizeObserver === \"undefined\") return;\n    const ro = new ResizeObserver(([e]) => setValue(e.contentRect.height));\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [ref, key, enabled]);\n  return enabled ? value : fallback;\n}\n"},{"path":"beamline/hooks/use-controllable.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-controllable.ts","content":"\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\n\n/**\n * One value in both forms (rules/CONVENTIONS.md): the prop while it is passed, otherwise the element's own copy that\n * starts at `initial` (a value, or a function that makes it once). The setter takes a value or an updater; an updater\n * reads the latest value even twice in one event, and every change reaches `onChange`, controlled or not.\n * `Next` is what a change can carry when it is narrower than the state: a range that starts empty (null) but is only\n * ever changed to a range.\n */\nexport function useControllable<T, Next extends T = T>(\n  value: T | undefined,\n  initial: T | (() => T),\n  onChange?: (next: Next) => void,\n) {\n  const [own, setOwn] = useState<T>(initial);\n  const current = value !== undefined ? value : own;\n  const latest = useRef(current);\n  latest.current = current;\n  const set = useCallback(\n    (next: Next | ((was: T) => Next)) => {\n      const resolved = typeof next === \"function\" ? (next as (was: T) => Next)(latest.current) : next;\n      latest.current = resolved;\n      if (value === undefined) setOwn(resolved);\n      onChange?.(resolved);\n    },\n    [value, onChange],\n  );\n  return [current, set] as const;\n}\n"},{"path":"beamline/hooks/use-deprecated-prop.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-deprecated-prop.ts","content":"\"use client\";\n\nimport { useEffect } from \"react\";\n\n// Bundlers replace NODE_ENV for browser builds; this declaration emits no Node dependency.\ndeclare const process: { env: { NODE_ENV?: string } };\n\nconst warned = new Set<string>();\n\n/** Report a legacy prop once after mount in development; production and server renders stay silent. */\nexport function useDeprecatedProp(component: string, prop: string, replacement: string, supplied: boolean) {\n  useEffect(() => {\n    if (process.env.NODE_ENV !== \"development\" || !supplied) return;\n    const key = `${component}.${prop}`;\n    if (warned.has(key)) return;\n    warned.add(key);\n    console.warn(`[Beamline] ${key} is deprecated. Use ${replacement} instead; the legacy prop still works.`);\n  }, [component, prop, replacement, supplied]);\n}\n"},{"path":"beamline/lib/utils.ts","type":"registry:file","target":"src/ui/beamline/lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/* The type roles (utilities.css) and the 2xs step are sizes, not text colours: without this, tailwind-merge reads\n   `text-overline` as a colour and drops it next to `text-ink-3`. A role and a size step still replace each other.\n   Likewise `font-title` is the look's title weight, not a family: read as a family it dropped `font-display` beside it,\n   and every merged page title lost the look's display face. */\nconst twMerge = extendTailwindMerge({\n  extend: {\n    classGroups: {\n      \"font-weight\": [{ font: [\"title\"] }],\n      \"font-family\": [{ font: [\"display\", \"numeric\", \"figure\"] }],\n      \"font-size\": [\n        {\n          text: [\n            \"2xs\",\n            \"display\",\n            \"title\",\n            \"heading\",\n            \"subheading\",\n            \"body\",\n            \"body-sm\",\n            \"reading\",\n            \"label\",\n            \"caption\",\n            \"overline\",\n            \"numeric\",\n            \"code\",\n          ],\n        },\n      ],\n    },\n  },\n});\n\n/** Merge class names, letting later Tailwind utilities win over earlier ones. */\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\n/** A small seeded random generator (LCG): the same seed gives the same sequence, for stable sample data and jitter. */\nexport function rng(seed = 1) {\n  let s = seed >>> 0 || 1;\n  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 2 ** 32;\n}\n\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n"}],"docs":"Item is in src/ui/beamline/components/item/item.tsx. The source is in src/ui/beamline (ui/beamline in a project without src/). After Tailwind, import its styles/beamline.css once, then styles/look-prism.css in your app stylesheet (use relative paths). Keep the shared base before all chosen looks. Wrap your app in <UIRoot preset={prism}> with UIRoot from its runtime/appearance and prism from its looks/prism.  shadcn replaces files when asked; use the Beamline managed-source CLI from the start for three-way upgrades that preserve edits.","meta":{"free":true,"version":"0.2.0-rc.42","library":"https://beamline.io/components/item"}}