import { HTMLAttributes, Ref } from 'vue';
import { MaskFn } from './engine/renderer/core';
/**
* Variant for the metal effect.
* - 'button' (default): pill-shaped 134×40 baseline with shaderScale 1.6
* - 'circle': compact 32×32 circle baseline with shaderScale 1.3
*
* In practice the wrapped child's measured dimensions drive the visible size —
* the variant only controls the shader sampling scale and ring thickness.
*/
export type MetalFxVariant = 'button' | 'circle';
/**
* Theme mode for the metal effect.
*
* - `auto` (default): follows the user's `prefers-color-scheme` and updates
* live when the OS / browser theme changes (also gracefully falls back to
* `dark` during SSR or when `matchMedia` is unavailable).
* - `dark`: pin to the dark-mode tunings regardless of system preference.
* - `light`: pin to the light-mode tunings regardless of system preference.
*
* Drive this from your app's theme state if you have a manual toggle that
* doesn't follow the OS — otherwise `auto` is the right default.
*/
export type MetalFxTheme = 'dark' | 'light' | 'auto';
/**
* Bundled preset names. Each preset ships both a dark and light mode block.
*/
export type MetalFxPreset = 'chromatic' | 'silver' | 'gold';
/**
* A reflection target: either a bare element/ref, or a ref plus a per-target
* `strength` multiplier (0..1+). Use the object form when a surface should
* only catch a faint echo of the metal — a container the button sits inside,
* for example — without turning down every other target.
*
* Upstream accepts `RefObject` (React refs are always
* objects); Vue template refs are `Ref`s, and a resolved element is accepted
* too so a `shallowRef`-free DOM lookup stays ergonomic.
*/
export type MetalFxReflectionTarget = HTMLElement | Ref | {
ref: Ref;
strength?: number;
};
/**
* Props for the MetalFx component.
*
* `children` / `className` / `style` travel as slot + template attributes
* (`class`/`style` are merged onto the root element by the wrapper itself, so
* the component declares `inheritAttrs: false` and re-applies them).
*/
export interface MetalFxProps extends /* @vue-ignore */ Omit {
/**
* Variant — controls the shader sampling scale + ring width.
* - `button` (default): pill-style ring at 1 px wide, scale 1.6
* - `circle`: compact circle with a 2 px ring, scale 1.3
*/
variant?: MetalFxVariant;
/**
* Color preset. All three presets ship both dark and light mode tunings —
* `theme` picks the right side at runtime.
* @default 'chromatic'
*/
preset?: MetalFxPreset;
/**
* Theme mode. `'auto'` (default) resolves via
* `matchMedia('(prefers-color-scheme: dark)')` and switches live when the
* OS theme changes. Pass `'dark'` or `'light'` to pin a specific mode.
* @default 'auto'
*/
theme?: MetalFxTheme;
/**
* Effect strength (0..1). Multiplies the shader bitmap opacity and the glow
* SVG alpha. The shader continues to animate at full intensity at any value;
* only the rendered alpha onto the host is scaled. Lighter values let the
* underlying child surface show through more strongly.
* @default 1
*/
strength?: number;
/**
* Multiplier on the glow only (halo + catch-light), on top of `strength`.
* Use it when the shader runs at low `strength` but the glow should still
* read — e.g. metal text. Clamped to 0..1 after multiplying.
* @default 1
*/
glowGain?: number;
/**
* Pause the shader animation. The visible canvas keeps the last painted
* frame so the metal silhouette stays on screen.
* @default false
*/
paused?: boolean;
/**
* Optional explicit border radius (CSS px). When omitted, MetalFx reads the
* computed border-radius of the wrapped child each resize.
*/
borderRadius?: number;
/**
* When true, MetalFx normalizes the host element's outer chrome (border /
* outline / box-shadow) so user-provided component styles don't clash with
* the metal ring. Inner fills, typography, and content remain untouched.
* @default true
*/
normalizeHostStyles?: boolean;
/**
* Neighbour elements that should receive a soft proximity reflection of the
* metal effect. Reflections only render when the resolved theme is `dark` —
* pass-through in light mode (no DOM scan, no per-frame work).
*
* Pass refs (or live elements) to the sibling DOM elements you want to
* receive the reflection (chips next to a send button, search field next to
* an Upgrade pill, ...).
*
* A target that *contains* the wrapped element (its parent card, say)
* receives the reflection on both the nearest vertical and horizontal
* inner edges. Pair with `{ ref, strength }` to keep that subtle.
*/
reflectionTargets?: ReadonlyArray;
/**
* Disable the wandering halo overlay. The shader ring still renders.
* @default false
*/
disableGlow?: boolean;
/**
* Light rim along the top inside edge of the ring — an inner shadow from
* above, like the one on metal text. `true` uses the design defaults
* (white 90 %, offset 1 px, blur 0.5 px); pass an object to tune.
* @default undefined (off)
*/
innerShadow?: boolean | {
offsetY?: number;
blur?: number;
alpha?: number;
color?: string;
};
/**
* Override the shader sampling scale. Larger values zoom into the shared
* shader (visibly bigger pattern features); smaller values zoom out.
* Defaults to the variant's baseline (1.6 for `'button'`, 1.3 for
* `'circle'`) multiplied by `scale`.
*/
shaderScale?: number;
/**
* Override the ring thickness in CSS pixels. Defaults to the variant's
* baseline (1 for `'button'`, 2 for `'circle'`) multiplied by `scale`.
*/
ringCssPx?: number;
/**
* Master scale multiplier for every absolute-pixel constant the engine
* uses internally. Set this when you render the wrapped element at a
* non-1× size (e.g. inside a CSS `zoom: 2` container, or when you've
* doubled all your Tailwind sizes by hand). It scales:
* - shader sampling (so pattern features grow proportionally)
* - ring thickness on the canvas
* - glow SVG stroke widths, blur radii, fade-circle radius, and the
* small inset/outward offsets that position the catch-light
* - reflection canvas stroke band, border-highlight thickness, and the
* reference draw width baseline
* @default 1
*/
scale?: number;
/**
* Custom alpha mask painter. When set, the shader is kept only where the
* mask paints (device px, origin at the wrapper's top-left) and the ring
* punch is skipped — for metal-filled text or glyphs. Pair with
* `disableGlow`; the glow assumes a ring perimeter.
*/
mask?: MaskFn;
/**
* How the glow is placed when `mask` is set. `'mask'` (default) samples
* points inside the mask and clips the halo to it — right for metal text.
* `'ring'` keeps the rounded-rect perimeter behaviour — a halo along the
* element's edge — for a full-fill mask like a badge.
*/
glowMode?: 'mask' | 'ring';
}
/**
* Public handle exposed by `TxMetalFx` through `defineExpose`, carrying the
* wrapped root element.
*
* Upstream forwards that node directly as the `forwardRef` value; Vue has no
* ref-as-value equivalent for components, so the element travels on a named
* handle (`` → `fx.value?.el`).
*/
export interface MetalFxRef {
el: HTMLDivElement | null;
}
/**
* Figma inner shadow on the text (1471:40930): white 90 %, offset 0/1,
* blur 0.5 — a hairline of light along the top inside edge of every glyph.
* CSS has no inner shadow for text, so it's computed: glyph alpha minus the
* same alpha shifted down by the offset leaves exactly that top rim, which is
* then blurred and drawn white on an overlay above the metal.
*/
export interface TextInnerShadow {
offsetY: number;
blur: number;
alpha: number;
}
/** Upstream default for `MetalText`'s `innerShadow`. */
export declare const FIGMA_INNER_SHADOW: TextInnerShadow;
/** Tuned on the demo's "Plan Pro" card. */
export declare const METAL_TEXT_DEFAULTS: Readonly<{
metalOpacity: 0.62;
shaderScale: 2.8;
glowGain: 2.5;
innerShadow: TextInnerShadow;
}>;
/**
* Props for the `TxMetalText` component.
*
* The label is normally the default slot (`Pro`);
* `children` is the upstream React spelling and still works as a fallback when
* the slot is empty. The glyph mask and the `aria-label` are painted from
* whichever text actually renders.
*/
export interface MetalTextProps {
/** Fallback label when the default slot is empty (upstream React prop). */
children?: string;
/** CSS `font` shorthand for the live span, e.g. `500 24px/1 Inter, sans-serif`. */
font: string;
/** Base text colour from the design; the metal composites over it. */
color: string;
strength?: number;
theme?: MetalFxTheme;
reflectionTargets?: ReadonlyArray;
/** Top-edge light rim inside the glyphs. Pass null to disable. */
innerShadow?: TextInnerShadow | null;
/** Halo on the glyphs. Off by default — the design has none. */
glow?: boolean;
/** Glow multiplier when `glow` is on. */
glowGain?: number;
/** How much metal shows over the base colour (0..1), multiplied with `strength`. */
metalOpacity?: number;
/** Zoom of the metal inside the glyphs. */
shaderScale?: number;
}
/** White core under a metal badge's label. */
export interface MetalBadgeCore {
r: number;
blur: number;
a: number;
size: number;
}
/** Tuned on the demo's "Live mode · New" card. */
export declare const METAL_BADGE_DEFAULTS: Readonly<{
metalOpacity: 0.8;
shaderScale: 1.6;
/** White core under the label: solid radius (% of ellipse), ramp width, opacity, ellipse size (% of box). */
core: MetalBadgeCore;
gradient: 0;
glow: 0.41;
}>;
/** Props for the `TxMetalBadge` component. */
export interface MetalBadgeProps {
/** Badge label. Annotated with `aria-label` on the pill's text layer. */
children?: string;
strength?: number;
theme?: MetalFxTheme;
/** Size multiplier on the Figma metrics (45×25, 12.222px). */
scale?: number;
reflectionTargets?: ReadonlyArray;
/** How much metal shows over the white fill (0..1), multiplied with `strength`. */
metalOpacity?: number;
shaderScale?: number;
core?: MetalBadgeCore;
/** Top→bottom white wash strength (0..1). */
gradient?: number;
/** Inner white glow strength (0..1). */
glow?: number;
textColor?: string;
}
/**
* Discriminates the bare-element shape of `MetalFxReflectionTarget` without
* touching the DOM (no `HTMLElement` during SSR). Type guard so the callers
* keep their narrowing.
*/
export declare function isElementTarget(target: MetalFxReflectionTarget): target is HTMLElement;