{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "password-generator-widget",
  "title": "Password Generator Widget",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/password-generator-widget/PasswordGeneratorWidget.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  Check,\n  Clock,\n  Copy,\n  Dice5,\n  Eye,\n  EyeOff,\n  Hash,\n  History,\n  KeyRound,\n  Lock,\n  RefreshCw,\n  Shield,\n  ShieldCheck,\n  Sliders,\n  Trash2,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\nimport { Slider } from '@/components/ui/slider'\nimport { Switch } from '@/components/ui/switch'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'\n\n// --- WORD LIST & CONSTANTS ---\nconst WORD_LIST = [\n  'anchor',\n  'apple',\n  'apron',\n  'arctic',\n  'arrow',\n  'atlas',\n  'autumn',\n  'bacon',\n  'badge',\n  'banner',\n  'beacon',\n  'blanket',\n  'breeze',\n  'bridge',\n  'cabin',\n  'cactus',\n  'camera',\n  'candle',\n  'canyon',\n  'carpet',\n  'castle',\n  'cedar',\n  'celestial',\n  'channel',\n  'cherry',\n  'chimney',\n  'cipher',\n  'circle',\n  'clay',\n  'cliff',\n  'clock',\n  'cloud',\n  'clover',\n  'cobalt',\n  'coffee',\n  'comet',\n  'compass',\n  'copper',\n  'coral',\n  'cosmos',\n  'crater',\n  'creek',\n  'crystal',\n  'curtain',\n  'delta',\n  'desert',\n  'diamond',\n  'dolphin',\n  'dragon',\n  'drift',\n  'eagle',\n  'earth',\n  'echo',\n  'eclipse',\n  'ember',\n  'emerald',\n  'engine',\n  'falcon',\n  'feather',\n  'fender',\n  'filter',\n  'flame',\n  'flask',\n  'forest',\n  'fossil',\n  'galaxy',\n  'garden',\n  'garlic',\n  'garnet',\n  'gem',\n  'glacier',\n  'glass',\n  'globe',\n  'granite',\n  'gravel',\n  'grove',\n  'harbor',\n  'haven',\n  'hawk',\n  'hazel',\n  'horizon',\n  'island',\n  'ivory',\n  'jungle',\n  'jupiter',\n  'kettle',\n  'lagoon',\n  'lantern',\n  'laser',\n  'lava',\n  'leaf',\n  'lemon',\n  'leopard',\n  'lighthouse',\n  'lightning',\n  'lotus',\n  'lunar',\n  'magnet',\n  'mango',\n  'maple',\n  'marble',\n  'matrix',\n  'meadow',\n  'meteor',\n  'mineral',\n  'mirror',\n  'monarch',\n  'moon',\n  'mosaic',\n  'mountain',\n  'nebula',\n  'nickel',\n  'oasis',\n  'ocean',\n  'olive',\n  'onyx',\n  'opal',\n  'orbit',\n  'orchid',\n  'oxygen',\n  'palace',\n  'panther',\n  'paradise',\n  'pebble',\n  'pelican',\n  'peacock',\n  'phoenix',\n  'pillar',\n  'planet',\n  'plasma',\n  'platinum',\n  'polar',\n  'prism',\n  'pulse',\n  'pyramid',\n  'quartz',\n  'quiver',\n  'radar',\n  'rainbow',\n  'raven',\n  'reef',\n  'ridge',\n  'ripple',\n  'river',\n  'rocket',\n  'ruby',\n  'safari',\n  'sail',\n  'sapphire',\n  'saturn',\n  'shadow',\n  'shield',\n  'sierra',\n  'silver',\n  'solstice',\n  'spark',\n  'sphere',\n  'spiral',\n  'spring',\n  'spruce',\n  'star',\n  'stone',\n  'summit',\n  'sunset',\n  'surge',\n  'timber',\n  'topaz',\n  'torch',\n  'trail',\n  'treasure',\n  'tundra',\n  'valley',\n  'vector',\n  'velvet',\n  'vessel',\n  'vintage',\n  'violet',\n  'vortex',\n  'voyage',\n  'vulcan',\n  'walnut',\n  'willow',\n  'wind',\n  'winter',\n  'zenith',\n  'zephyr',\n]\n\nconst UPPERCASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\nconst LOWERCASE_CHARS = 'abcdefghijklmnopqrstuvwxyz'\nconst NUMBER_CHARS = '0123456789'\nconst SYMBOL_CHARS = '!@#$%^&*()_+-=[]{}|;:,.<>?'\nconst AMBIGUOUS_CHARS = new Set(['l', '1', 'I', 'O', '0', 'o', '|', '`', \"'\", '\"', ',', ';'])\n\nexport interface HistoryItem {\n  id: string\n  secret: string\n  mode: 'password' | 'passphrase' | 'pin'\n  entropy: number\n  strengthLabel: string\n  createdAt: string\n}\n\nfunction getRandomInt(max: number): number {\n  if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {\n    const array = new Uint32Array(1)\n    window.crypto.getRandomValues(array)\n    return array[0] % max\n  }\n  return Math.floor(Math.random() * max)\n}\n\nexport function PasswordGeneratorWidget({ className }: { className?: string }) {\n  // --- STATE ---\n  const [mode, setMode] = React.useState<'password' | 'passphrase' | 'pin'>('password')\n  const [passwordLength, setPasswordLength] = React.useState(20)\n  const [includeUpper, setIncludeUpper] = React.useState(true)\n  const [includeLower, setIncludeLower] = React.useState(true)\n  const [includeNumbers, setIncludeNumbers] = React.useState(true)\n  const [includeSymbols, setIncludeSymbols] = React.useState(true)\n  const [avoidAmbiguous, setAvoidAmbiguous] = React.useState(true)\n\n  // Passphrase state\n  const [wordCount, setWordCount] = React.useState(5)\n  const [separator, setSeparator] = React.useState('-')\n  const [capitalizeWords, setCapitalizeWords] = React.useState(true)\n  const [includeNumberInPassphrase, setIncludeNumberInPassphrase] = React.useState(true)\n\n  // PIN state\n  const [pinLength, setPinLength] = React.useState(6)\n  const [avoidPinRepeats, setAvoidPinRepeats] = React.useState(true)\n\n  // Output & UI state\n  const [currentSecret, setCurrentSecret] = React.useState('')\n  const [copied, setCopied] = React.useState(false)\n  const [isSpinning, setIsSpinning] = React.useState(false)\n  const [copiedHistoryId, setCopiedHistoryId] = React.useState<string | null>(null)\n  const [revealedHistoryIds, setRevealedHistoryIds] = React.useState<Set<string>>(new Set())\n\n  // History state\n  const [history, setHistory] = React.useState<HistoryItem[]>([\n    {\n      id: 'hist-1',\n      secret: 'kX9#mQ4$vL2!pW8*jT7^',\n      mode: 'password',\n      entropy: 114,\n      strengthLabel: 'Very Strong',\n      createdAt: 'Just now',\n    },\n    {\n      id: 'hist-2',\n      secret: 'Celestial-Falcon-Lagoon-Timber-83',\n      mode: 'passphrase',\n      entropy: 48,\n      strengthLabel: 'Fair',\n      createdAt: '4m ago',\n    },\n    {\n      id: 'hist-3',\n      secret: '928401',\n      mode: 'pin',\n      entropy: 20,\n      strengthLabel: 'Very Weak',\n      createdAt: '12m ago',\n    },\n  ])\n\n  // --- ENTROPY & STRENGTH METRICS ---\n  const currentStrength = React.useMemo(() => {\n    let entropy = 0\n    if (mode === 'password') {\n      let poolSize = 0\n      if (includeUpper) poolSize += avoidAmbiguous ? 24 : 26\n      if (includeLower) poolSize += avoidAmbiguous ? 24 : 26\n      if (includeNumbers) poolSize += avoidAmbiguous ? 8 : 10\n      if (includeSymbols) poolSize += avoidAmbiguous ? 26 : 28\n      entropy = poolSize > 1 ? Math.round(passwordLength * Math.log2(poolSize)) : 0\n    } else if (mode === 'passphrase') {\n      entropy = wordCount * Math.log2(WORD_LIST.length)\n      if (capitalizeWords) entropy += wordCount * 1\n      if (includeNumberInPassphrase) entropy += Math.log2(90)\n      entropy = Math.round(entropy)\n    } else {\n      entropy = Math.round(pinLength * Math.log2(10))\n    }\n\n    if (entropy < 30) {\n      return {\n        entropy,\n        score: 1,\n        label: 'Very Weak',\n        crackTime: 'Instant (< 1 ms)',\n        color: 'text-destructive',\n        barClass: 'bg-destructive',\n      }\n    }\n    if (entropy < 50) {\n      return {\n        entropy,\n        score: 2,\n        label: 'Weak',\n        crackTime: 'A few minutes',\n        color: 'text-orange-500',\n        barClass: 'bg-orange-500',\n      }\n    }\n    if (entropy < 70) {\n      return {\n        entropy,\n        score: 3,\n        label: 'Fair',\n        crackTime: 'Several months',\n        color: 'text-warning',\n        barClass: 'bg-warning',\n      }\n    }\n    if (entropy < 95) {\n      return {\n        entropy,\n        score: 4,\n        label: 'Strong',\n        crackTime: 'Hundreds of years',\n        color: 'text-success',\n        barClass: 'bg-success',\n      }\n    }\n    return {\n      entropy,\n      score: 4,\n      label: 'Very Strong',\n      crackTime: '100+ billion years',\n      color: 'text-success',\n      barClass: 'bg-success',\n    }\n  }, [\n    mode,\n    passwordLength,\n    includeUpper,\n    includeLower,\n    includeNumbers,\n    includeSymbols,\n    avoidAmbiguous,\n    wordCount,\n    capitalizeWords,\n    includeNumberInPassphrase,\n    pinLength,\n  ])\n\n  // --- GENERATION FUNCTIONS ---\n  const generatePassword = React.useCallback((): string => {\n    let pool = ''\n    const requiredChars: string[] = []\n\n    const filterPool = (chars: string) => {\n      if (!avoidAmbiguous) return chars\n      return chars\n        .split('')\n        .filter((c) => !AMBIGUOUS_CHARS.has(c))\n        .join('')\n    }\n\n    const uppers = filterPool(UPPERCASE_CHARS)\n    const lowers = filterPool(LOWERCASE_CHARS)\n    const numbers = filterPool(NUMBER_CHARS)\n    const symbols = filterPool(SYMBOL_CHARS)\n\n    if (includeUpper && uppers.length > 0) {\n      pool += uppers\n      requiredChars.push(uppers[getRandomInt(uppers.length)])\n    }\n    if (includeLower && lowers.length > 0) {\n      pool += lowers\n      requiredChars.push(lowers[getRandomInt(lowers.length)])\n    }\n    if (includeNumbers && numbers.length > 0) {\n      pool += numbers\n      requiredChars.push(numbers[getRandomInt(numbers.length)])\n    }\n    if (includeSymbols && symbols.length > 0) {\n      pool += symbols\n      requiredChars.push(symbols[getRandomInt(symbols.length)])\n    }\n\n    if (pool.length === 0) {\n      pool = lowers.length > 0 ? lowers : 'abcdefghjkmnpqrstuvwxyz'\n      requiredChars.push(pool[getRandomInt(pool.length)])\n    }\n\n    const result: string[] = [...requiredChars]\n    for (let i = result.length; i < passwordLength; i++) {\n      result.push(pool[getRandomInt(pool.length)])\n    }\n\n    // Fisher-Yates shuffle\n    for (let i = result.length - 1; i > 0; i--) {\n      const j = getRandomInt(i + 1)\n      const temp = result[i]\n      result[i] = result[j]\n      result[j] = temp\n    }\n\n    return result.join('')\n  }, [avoidAmbiguous, includeLower, includeNumbers, includeSymbols, includeUpper, passwordLength])\n\n  const generatePassphrase = React.useCallback((): string => {\n    const chosenWords: string[] = []\n    for (let i = 0; i < wordCount; i++) {\n      let word = WORD_LIST[getRandomInt(WORD_LIST.length)]\n      if (capitalizeWords) {\n        word = word.charAt(0).toUpperCase() + word.slice(1)\n      }\n      chosenWords.push(word)\n    }\n\n    if (includeNumberInPassphrase) {\n      const num = getRandomInt(90) + 10 // 2-digit number (10-99)\n      chosenWords.push(num.toString())\n    }\n\n    return chosenWords.join(separator)\n  }, [capitalizeWords, includeNumberInPassphrase, separator, wordCount])\n\n  const generatePin = React.useCallback((): string => {\n    const digits: string[] = []\n    for (let i = 0; i < pinLength; i++) {\n      let nextDigit = getRandomInt(10).toString()\n      if (avoidPinRepeats && digits.length > 0 && nextDigit === digits[digits.length - 1]) {\n        nextDigit = ((parseInt(nextDigit, 10) + 1 + getRandomInt(8)) % 10).toString()\n      }\n      digits.push(nextDigit)\n    }\n    return digits.join('')\n  }, [avoidPinRepeats, pinLength])\n\n  const regenerateSecret = React.useCallback(\n    (recordHistory = false) => {\n      let secret = ''\n      if (mode === 'password') {\n        secret = generatePassword()\n      } else if (mode === 'passphrase') {\n        secret = generatePassphrase()\n      } else {\n        secret = generatePin()\n      }\n\n      setCurrentSecret(secret)\n\n      if (recordHistory && secret) {\n        const ent = currentStrength.entropy\n        const item: HistoryItem = {\n          id: `hist-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,\n          secret,\n          mode,\n          entropy: ent,\n          strengthLabel: currentStrength.label,\n          createdAt: 'Just now',\n        }\n        setHistory((prev) => [item, ...prev.slice(0, 7)])\n      }\n    },\n    [currentStrength.entropy, currentStrength.label, generatePassphrase, generatePassword, generatePin, mode],\n  )\n\n  const handleManualRegenerate = React.useCallback(() => {\n    setIsSpinning(true)\n    regenerateSecret(true)\n    setTimeout(() => {\n      setIsSpinning(false)\n    }, 400)\n  }, [regenerateSecret])\n\n  // Ensure at least one character type stays active in password mode\n  const handleUpperChange = (val: boolean) => {\n    if (!val && !includeLower && !includeNumbers && !includeSymbols) return\n    setIncludeUpper(val)\n  }\n\n  const handleLowerChange = (val: boolean) => {\n    if (!val && !includeUpper && !includeNumbers && !includeSymbols) return\n    setIncludeLower(val)\n  }\n\n  const handleNumbersChange = (val: boolean) => {\n    if (!val && !includeUpper && !includeLower && !includeSymbols) return\n    setIncludeNumbers(val)\n  }\n\n  const handleSymbolsChange = (val: boolean) => {\n    if (!val && !includeUpper && !includeLower && !includeNumbers) return\n    setIncludeSymbols(val)\n  }\n\n  // Reactive regeneration on parameter change\n  React.useEffect(() => {\n    regenerateSecret(false)\n  }, [\n    mode,\n    passwordLength,\n    includeUpper,\n    includeLower,\n    includeNumbers,\n    includeSymbols,\n    avoidAmbiguous,\n    wordCount,\n    separator,\n    capitalizeWords,\n    includeNumberInPassphrase,\n    pinLength,\n    avoidPinRepeats,\n  ])\n\n  // --- CHARACTER FORMATTING FOR SYNTAX HIGHLIGHTING ---\n  const formattedCharacters = React.useMemo(() => {\n    if (!currentSecret) return []\n    return currentSecret.split('').map((char) => {\n      let type: 'number' | 'symbol' | 'letter' | 'separator' = 'letter'\n      if (/[0-9]/.test(char)) {\n        type = 'number'\n      } else if (/[-_. /]/.test(char)) {\n        type = 'separator'\n      } else if (/[^a-zA-Z0-9]/.test(char)) {\n        type = 'symbol'\n      }\n      return { char, type }\n    })\n  }, [currentSecret])\n\n  const formatItemCharacters = (text: string) => {\n    return text.split('').map((char) => {\n      let type: 'number' | 'symbol' | 'letter' | 'separator' = 'letter'\n      if (/[0-9]/.test(char)) {\n        type = 'number'\n      } else if (/[-_. /]/.test(char)) {\n        type = 'separator'\n      } else if (/[^a-zA-Z0-9]/.test(char)) {\n        type = 'symbol'\n      }\n      return { char, type }\n    })\n  }\n\n  // --- CLIPBOARD ---\n  const copySecret = async (text: string, isHistory = false, historyId = '') => {\n    try {\n      if (navigator?.clipboard?.writeText) {\n        await navigator.clipboard.writeText(text)\n      } else {\n        const textarea = document.createElement('textarea')\n        textarea.value = text\n        document.body.appendChild(textarea)\n        textarea.select()\n        document.execCommand('copy')\n        document.body.removeChild(textarea)\n      }\n\n      if (isHistory && historyId) {\n        setCopiedHistoryId(historyId)\n        setTimeout(() => {\n          setCopiedHistoryId((prev) => (prev === historyId ? null : prev))\n        }, 2000)\n      } else {\n        setCopied(true)\n        setTimeout(() => {\n          setCopied(false)\n        }, 2000)\n      }\n    } catch (err) {\n      console.error('Failed to copy to clipboard', err)\n    }\n  }\n\n  const toggleHistoryReveal = (id: string) => {\n    setRevealedHistoryIds((prev) => {\n      const next = new Set(prev)\n      if (next.has(id)) {\n        next.delete(id)\n      } else {\n        next.add(id)\n      }\n      return next\n    })\n  }\n\n  const clearHistory = () => {\n    setHistory([])\n  }\n\n  return (\n    <div data-slot=\"password-generator-widget\" className={cn('mx-auto w-full max-w-4xl space-y-6', className)}>\n      {/* HERO: GENERATOR OUTPUT CARD */}\n      <Card className=\"border-border overflow-hidden shadow-xs\">\n        <CardHeader className=\"border-border/50 bg-muted/20 border-b pb-3\">\n          <div className=\"flex flex-wrap items-center justify-between gap-2\">\n            <div className=\"flex items-center gap-2\">\n              <div className=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n                <KeyRound className=\"size-4\" />\n              </div>\n              <div>\n                <CardTitle className=\"text-base font-semibold\">Generated Secret</CardTitle>\n                <CardDescription className=\"text-xs\">\n                  Client-side cryptographic output &amp; entropy estimation\n                </CardDescription>\n              </div>\n            </div>\n            <div className=\"flex items-center gap-2\">\n              <Badge variant=\"outline\" className=\"gap-1.5 text-xs font-normal\">\n                <span className=\"bg-success size-2 animate-pulse rounded-full\" />\n                100% Client-Side\n              </Badge>\n            </div>\n          </div>\n        </CardHeader>\n\n        <CardContent className=\"space-y-6 pt-6\">\n          {/* Monospace Output Box */}\n          <div className=\"border-border/80 bg-muted/40 dark:bg-muted/20 relative flex min-h-[4.75rem] w-full flex-col justify-center gap-4 rounded-xl border p-4 sm:flex-row sm:items-center sm:justify-between\">\n            <div className=\"flex flex-wrap items-center gap-x-0.5 gap-y-1 pr-2 font-mono text-lg font-medium tracking-wider break-all select-all md:text-xl\">\n              {formattedCharacters.length > 0 ? (\n                formattedCharacters.map((item, idx) => (\n                  <span\n                    key={idx}\n                    className={cn(\n                      item.type === 'number' && 'text-info font-semibold',\n                      item.type === 'symbol' && 'text-warning font-semibold',\n                      item.type === 'separator' && 'text-muted-foreground px-0.5 font-normal',\n                      item.type === 'letter' && 'text-foreground font-medium',\n                    )}\n                  >\n                    {item.char}\n                  </span>\n                ))\n              ) : (\n                <span className=\"text-muted-foreground text-sm\">Generating secret...</span>\n              )}\n            </div>\n\n            {/* Quick Action Buttons */}\n            <div className=\"flex shrink-0 items-center gap-2 self-end sm:self-center\">\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                className=\"h-9 cursor-pointer gap-1.5 px-3 active:scale-95\"\n                onClick={handleManualRegenerate}\n              >\n                <RefreshCw className={cn('size-4', isSpinning && 'text-primary animate-spin')} />\n                <span className=\"text-xs font-medium\">Regenerate</span>\n              </Button>\n\n              <Button\n                variant=\"default\"\n                size=\"sm\"\n                className={cn(\n                  'h-9 cursor-pointer gap-1.5 px-3.5 active:scale-95',\n                  copied && 'bg-success hover:bg-success text-white',\n                )}\n                onClick={() => copySecret(currentSecret)}\n              >\n                {copied ? <Check className=\"size-4 stroke-[2.5]\" /> : <Copy className=\"size-4\" />}\n                <span className=\"text-xs font-medium\">{copied ? 'Copied!' : 'Copy'}</span>\n              </Button>\n            </div>\n          </div>\n\n          {/* Entropy & Strength Meter */}\n          <div className=\"border-border/60 bg-muted/10 space-y-2.5 rounded-lg border p-4\">\n            <div className=\"flex flex-wrap items-center justify-between gap-2 text-xs\">\n              <div className=\"flex items-center gap-2\">\n                <ShieldCheck className={cn('size-4', currentStrength.color)} />\n                <span className=\"text-foreground font-semibold\">{currentStrength.label}</span>\n                <span className=\"text-muted-foreground font-mono\">{currentStrength.entropy}-bit entropy</span>\n              </div>\n              <div className=\"text-muted-foreground flex items-center gap-1.5\">\n                <Clock className=\"size-3.5\" />\n                <span>Crack time:</span>\n                <span className=\"text-foreground font-medium\">{currentStrength.crackTime}</span>\n              </div>\n            </div>\n\n            {/* 4-Segment Strength Bar */}\n            <div className=\"grid h-2 w-full grid-cols-4 gap-1.5 overflow-hidden rounded-full\">\n              <div\n                className={cn(\n                  'rounded-full transition-colors duration-300',\n                  currentStrength.score >= 1 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted',\n                )}\n              />\n              <div\n                className={cn(\n                  'rounded-full transition-colors duration-300',\n                  currentStrength.score >= 2 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted',\n                )}\n              />\n              <div\n                className={cn(\n                  'rounded-full transition-colors duration-300',\n                  currentStrength.score >= 3 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted',\n                )}\n              />\n              <div\n                className={cn(\n                  'rounded-full transition-colors duration-300',\n                  currentStrength.score >= 4 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted',\n                )}\n              />\n            </div>\n\n            {/* Monospace Character Type Legend */}\n            <div className=\"text-muted-foreground flex flex-wrap items-center gap-4 pt-1 text-xs\">\n              <span className=\"flex items-center gap-1.5\">\n                <span className=\"bg-foreground size-2 rounded-full\" />\n                Letters\n              </span>\n              <span className=\"flex items-center gap-1.5\">\n                <span className=\"bg-info size-2 rounded-full\" />\n                Numbers (0-9)\n              </span>\n              <span className=\"flex items-center gap-1.5\">\n                <span className=\"bg-warning size-2 rounded-full\" />\n                Symbols (!@#$)\n              </span>\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n\n      {/* GENERATOR CONFIGURATION CARD */}\n      <Card className=\"border-border shadow-xs\">\n        <CardHeader className=\"border-border/50 border-b pb-3\">\n          <div className=\"flex items-center gap-2\">\n            <div className=\"bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-lg\">\n              <Sliders className=\"size-4\" />\n            </div>\n            <div>\n              <CardTitle className=\"text-base font-semibold\">Generator Configuration</CardTitle>\n              <CardDescription className=\"text-xs\">\n                Customize character pools, length constraints, and format patterns\n              </CardDescription>\n            </div>\n          </div>\n        </CardHeader>\n\n        <CardContent className=\"pt-6\">\n          <Tabs\n            value={mode}\n            onValueChange={(val) => setMode(val as 'password' | 'passphrase' | 'pin')}\n            className=\"w-full\"\n          >\n            <TabsList className=\"mb-6 grid w-full grid-cols-3\">\n              <TabsTrigger value=\"password\" className=\"gap-1.5 text-xs\">\n                <Lock className=\"size-3.5\" />\n                <span>Random Password</span>\n              </TabsTrigger>\n              <TabsTrigger value=\"passphrase\" className=\"gap-1.5 text-xs\">\n                <Dice5 className=\"size-3.5\" />\n                <span>Passphrase</span>\n              </TabsTrigger>\n              <TabsTrigger value=\"pin\" className=\"gap-1.5 text-xs\">\n                <Hash className=\"size-3.5\" />\n                <span>PIN Code</span>\n              </TabsTrigger>\n            </TabsList>\n\n            {/* TAB 1: RANDOM PASSWORD */}\n            <TabsContent value=\"password\" className=\"mt-0 space-y-6\">\n              {/* Length Control */}\n              <div className=\"space-y-3\">\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-foreground text-sm font-medium\">Password Length</label>\n                  <Badge variant=\"secondary\" className=\"font-mono text-xs font-semibold tabular-nums\">\n                    {passwordLength} characters\n                  </Badge>\n                </div>\n\n                <Slider\n                  value={[passwordLength]}\n                  min={8}\n                  max={64}\n                  step={1}\n                  className=\"w-full py-1\"\n                  onValueChange={([val]) => setPasswordLength(val)}\n                />\n\n                {/* Preset length buttons */}\n                <div className=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span className=\"text-muted-foreground mr-1 text-xs\">Presets:</span>\n                  {[12, 16, 20, 24, 32, 48, 64].map((preset) => (\n                    <button\n                      key={preset}\n                      type=\"button\"\n                      className={cn(\n                        'cursor-pointer rounded-md border px-2.5 py-1 text-xs font-medium transition-colors',\n                        passwordLength === preset\n                          ? 'bg-primary text-primary-foreground border-primary shadow-xs'\n                          : 'bg-muted/40 text-muted-foreground border-border hover:bg-muted hover:text-foreground',\n                      )}\n                      onClick={() => setPasswordLength(preset)}\n                    >\n                      {preset}\n                    </button>\n                  ))}\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* Character Type Switches */}\n              <div className=\"space-y-3\">\n                <label className=\"text-muted-foreground text-xs font-medium\">Character Pools</label>\n\n                <div className=\"border-border/80 bg-muted/20 space-y-3 rounded-lg border p-4\">\n                  {/* Uppercase */}\n                  <div className=\"flex items-center justify-between\">\n                    <div className=\"space-y-0.5\">\n                      <div className=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                        <span>Uppercase Letters</span>\n                        <span className=\"text-muted-foreground font-mono text-xs font-normal\">(A-Z)</span>\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">Include capital Latin characters</p>\n                    </div>\n                    <Switch checked={includeUpper} onCheckedChange={handleUpperChange} />\n                  </div>\n\n                  <Separator />\n\n                  {/* Lowercase */}\n                  <div className=\"flex items-center justify-between\">\n                    <div className=\"space-y-0.5\">\n                      <div className=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                        <span>Lowercase Letters</span>\n                        <span className=\"text-muted-foreground font-mono text-xs font-normal\">(a-z)</span>\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">Include small Latin characters</p>\n                    </div>\n                    <Switch checked={includeLower} onCheckedChange={handleLowerChange} />\n                  </div>\n\n                  <Separator />\n\n                  {/* Numbers */}\n                  <div className=\"flex items-center justify-between\">\n                    <div className=\"space-y-0.5\">\n                      <div className=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                        <span>Numbers</span>\n                        <span className=\"text-info font-mono text-xs font-semibold\">(0-9)</span>\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">Include numeric digits 0 through 9</p>\n                    </div>\n                    <Switch checked={includeNumbers} onCheckedChange={handleNumbersChange} />\n                  </div>\n\n                  <Separator />\n\n                  {/* Symbols */}\n                  <div className=\"flex items-center justify-between\">\n                    <div className=\"space-y-0.5\">\n                      <div className=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                        <span>Special Symbols</span>\n                        <span className=\"text-warning font-mono text-xs font-semibold\">(!@#$%^&*)</span>\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">Include non-alphanumeric punctuation marks</p>\n                    </div>\n                    <Switch checked={includeSymbols} onCheckedChange={handleSymbolsChange} />\n                  </div>\n\n                  <Separator />\n\n                  {/* Avoid Ambiguous */}\n                  <div className=\"flex items-center justify-between\">\n                    <div className=\"space-y-0.5\">\n                      <div className=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                        <span>Avoid Ambiguous Characters</span>\n                        <span className=\"text-muted-foreground font-mono text-xs font-normal\">(l, 1, I, O, 0)</span>\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">Exclude confusing lookalike letters and digits</p>\n                    </div>\n                    <Switch checked={avoidAmbiguous} onCheckedChange={setAvoidAmbiguous} />\n                  </div>\n                </div>\n              </div>\n            </TabsContent>\n\n            {/* TAB 2: MEMORABLE PASSPHRASE */}\n            <TabsContent value=\"passphrase\" className=\"mt-0 space-y-6\">\n              {/* Word Count Control */}\n              <div className=\"space-y-3\">\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-foreground text-sm font-medium\">Number of Words</label>\n                  <Badge variant=\"secondary\" className=\"font-mono text-xs font-semibold tabular-nums\">\n                    {wordCount} words\n                  </Badge>\n                </div>\n\n                <Slider\n                  value={[wordCount]}\n                  min={3}\n                  max={8}\n                  step={1}\n                  className=\"w-full py-1\"\n                  onValueChange={([val]) => setWordCount(val)}\n                />\n\n                {/* Preset word count buttons */}\n                <div className=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span className=\"text-muted-foreground mr-1 text-xs\">Presets:</span>\n                  {[3, 4, 5, 6, 8].map((preset) => (\n                    <button\n                      key={preset}\n                      type=\"button\"\n                      className={cn(\n                        'cursor-pointer rounded-md border px-2.5 py-1 text-xs font-medium transition-colors',\n                        wordCount === preset\n                          ? 'bg-primary text-primary-foreground border-primary shadow-xs'\n                          : 'bg-muted/40 text-muted-foreground border-border hover:bg-muted hover:text-foreground',\n                      )}\n                      onClick={() => setWordCount(preset)}\n                    >\n                      {preset} words\n                    </button>\n                  ))}\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* Separator Selector */}\n              <div className=\"space-y-3\">\n                <label className=\"text-muted-foreground text-xs font-medium\">Word Separator</label>\n\n                <div className=\"grid grid-cols-5 gap-2\">\n                  {[\n                    { id: '-', label: 'Hyphen (-)' },\n                    { id: '_', label: 'Under (_)' },\n                    { id: '.', label: 'Period (.)' },\n                    { id: ' ', label: 'Space ( )' },\n                    { id: '/', label: 'Slash (/)' },\n                  ].map((sep) => (\n                    <button\n                      key={sep.id}\n                      type=\"button\"\n                      className={cn(\n                        'flex cursor-pointer flex-col items-center justify-center rounded-lg border p-2.5 text-xs font-medium transition-colors',\n                        separator === sep.id\n                          ? 'bg-primary/10 border-primary text-primary font-semibold shadow-xs'\n                          : 'border-border bg-background hover:bg-muted/50 text-muted-foreground hover:text-foreground',\n                      )}\n                      onClick={() => setSeparator(sep.id)}\n                    >\n                      <span className=\"font-mono text-sm font-semibold\">{sep.id === ' ' ? '␣' : sep.id}</span>\n                      <span className=\"mt-0.5 truncate text-xs\">{sep.label.split(' ')[0]}</span>\n                    </button>\n                  ))}\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* Passphrase Switches */}\n              <div className=\"border-border/80 bg-muted/20 space-y-3 rounded-lg border p-4\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"space-y-0.5\">\n                    <div className=\"text-foreground text-sm font-medium\">Capitalize Words</div>\n                    <p className=\"text-muted-foreground text-xs\">Transform each word into TitleCase for easy reading</p>\n                  </div>\n                  <Switch checked={capitalizeWords} onCheckedChange={setCapitalizeWords} />\n                </div>\n\n                <Separator />\n\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"space-y-0.5\">\n                    <div className=\"text-foreground text-sm font-medium\">Append Random Number</div>\n                    <p className=\"text-muted-foreground text-xs\">\n                      Add a 2-digit number (10-99) for additional security\n                    </p>\n                  </div>\n                  <Switch checked={includeNumberInPassphrase} onCheckedChange={setIncludeNumberInPassphrase} />\n                </div>\n              </div>\n            </TabsContent>\n\n            {/* TAB 3: PIN CODE */}\n            <TabsContent value=\"pin\" className=\"mt-0 space-y-6\">\n              {/* Length Control */}\n              <div className=\"space-y-3\">\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-foreground text-sm font-medium\">PIN Length</label>\n                  <Badge variant=\"secondary\" className=\"font-mono text-xs font-semibold tabular-nums\">\n                    {pinLength} digits\n                  </Badge>\n                </div>\n\n                <Slider\n                  value={[pinLength]}\n                  min={4}\n                  max={16}\n                  step={1}\n                  className=\"w-full py-1\"\n                  onValueChange={([val]) => setPinLength(val)}\n                />\n\n                {/* Preset PIN length buttons */}\n                <div className=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span className=\"text-muted-foreground mr-1 text-xs\">Presets:</span>\n                  {[4, 6, 8, 10, 12, 16].map((preset) => (\n                    <button\n                      key={preset}\n                      type=\"button\"\n                      className={cn(\n                        'cursor-pointer rounded-md border px-2.5 py-1 text-xs font-medium transition-colors',\n                        pinLength === preset\n                          ? 'bg-primary text-primary-foreground border-primary shadow-xs'\n                          : 'bg-muted/40 text-muted-foreground border-border hover:bg-muted hover:text-foreground',\n                      )}\n                      onClick={() => setPinLength(preset)}\n                    >\n                      {preset} digits\n                    </button>\n                  ))}\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* PIN Rules */}\n              <div className=\"border-border/80 bg-muted/20 space-y-3 rounded-lg border p-4\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"space-y-0.5\">\n                    <div className=\"text-foreground text-sm font-medium\">Avoid Consecutive Repeated Digits</div>\n                    <p className=\"text-muted-foreground text-xs\">\n                      Prevent obvious repetitive patterns like &ldquo;1122&rdquo; or &ldquo;8888&rdquo;\n                    </p>\n                  </div>\n                  <Switch checked={avoidPinRepeats} onCheckedChange={setAvoidPinRepeats} />\n                </div>\n              </div>\n            </TabsContent>\n          </Tabs>\n        </CardContent>\n      </Card>\n\n      {/* RECENT HISTORY CARD */}\n      <Card className=\"border-border shadow-xs\">\n        <CardHeader className=\"border-border/50 border-b pb-3\">\n          <div className=\"flex flex-wrap items-center justify-between gap-2\">\n            <div className=\"flex items-center gap-2\">\n              <div className=\"bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-lg\">\n                <History className=\"size-4\" />\n              </div>\n              <div>\n                <CardTitle className=\"text-base font-semibold\">Generated History</CardTitle>\n                <CardDescription className=\"text-xs\">\n                  Stored in volatile browser memory only &bull; Cleared on page refresh\n                </CardDescription>\n              </div>\n            </div>\n            {history.length > 0 && (\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"text-muted-foreground hover:text-destructive h-8 cursor-pointer gap-1.5 text-xs\"\n                onClick={clearHistory}\n              >\n                <Trash2 className=\"size-3.5\" />\n                <span>Clear History</span>\n              </Button>\n            )}\n          </div>\n        </CardHeader>\n\n        <CardContent className=\"pt-4\">\n          {history.length === 0 ? (\n            <div className=\"text-muted-foreground py-8 text-center text-xs\">\n              No recent secrets in session history. Regenerate or copy above to log items here.\n            </div>\n          ) : (\n            <div className=\"divide-border/60 divide-y\">\n              {history.map((item) => (\n                <div\n                  key={item.id}\n                  className=\"group hover:bg-muted/30 flex flex-col justify-between gap-3 rounded-md px-1 py-3 transition-colors sm:flex-row sm:items-center\"\n                >\n                  <div className=\"flex min-w-0 items-center gap-3\">\n                    <div className=\"bg-muted text-muted-foreground flex size-7 shrink-0 items-center justify-center rounded-md\">\n                      {item.mode === 'password' && <Lock className=\"size-3.5\" />}\n                      {item.mode === 'passphrase' && <Dice5 className=\"size-3.5\" />}\n                      {item.mode === 'pin' && <Hash className=\"size-3.5\" />}\n                    </div>\n\n                    <div className=\"min-w-0 space-y-1\">\n                      <div className=\"flex items-center gap-2\">\n                        {/* Monospace secret or masked bullets */}\n                        <div className=\"max-w-[18rem] truncate font-mono text-xs font-medium md:max-w-md\">\n                          {revealedHistoryIds.has(item.id) ? (\n                            formatItemCharacters(item.secret).map((subChar, idx) => (\n                              <span\n                                key={idx}\n                                className={cn(\n                                  subChar.type === 'number' && 'text-info font-semibold',\n                                  subChar.type === 'symbol' && 'text-warning font-semibold',\n                                  subChar.type === 'separator' && 'text-muted-foreground font-normal',\n                                  subChar.type === 'letter' && 'text-foreground',\n                                )}\n                              >\n                                {subChar.char}\n                              </span>\n                            ))\n                          ) : (\n                            <span className=\"text-muted-foreground tracking-widest\">\n                              &bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;\n                            </span>\n                          )}\n                        </div>\n\n                        {/* Reveal Toggle */}\n                        <button\n                          type=\"button\"\n                          className=\"text-muted-foreground hover:text-foreground cursor-pointer p-0.5 transition-colors\"\n                          title=\"Toggle Visibility\"\n                          onClick={() => toggleHistoryReveal(item.id)}\n                        >\n                          {revealedHistoryIds.has(item.id) ? (\n                            <EyeOff className=\"size-3.5\" />\n                          ) : (\n                            <Eye className=\"size-3.5\" />\n                          )}\n                        </button>\n                      </div>\n\n                      <div className=\"text-muted-foreground flex items-center gap-2 text-xs\">\n                        <Badge variant=\"outline\" className=\"px-1.5 py-0 text-xs font-normal\">\n                          {item.mode}\n                        </Badge>\n                        <span>&bull;</span>\n                        <span>\n                          {item.entropy}-bit ({item.strengthLabel})\n                        </span>\n                        <span>&bull;</span>\n                        <span>{item.createdAt}</span>\n                      </div>\n                    </div>\n                  </div>\n\n                  <div className=\"flex shrink-0 items-center gap-2 self-end sm:self-center\">\n                    <Button\n                      variant=\"outline\"\n                      size=\"sm\"\n                      className={cn(\n                        'h-8 cursor-pointer gap-1.5 px-2.5 text-xs active:scale-95',\n                        copiedHistoryId === item.id && 'bg-success hover:bg-success text-white',\n                      )}\n                      onClick={() => copySecret(item.secret, true, item.id)}\n                    >\n                      {copiedHistoryId === item.id ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n                      <span>{copiedHistoryId === item.id ? 'Copied' : 'Copy'}</span>\n                    </Button>\n                  </div>\n                </div>\n              ))}\n            </div>\n          )}\n        </CardContent>\n        <CardFooter className=\"text-muted-foreground border-border/40 flex items-center justify-between border-t pt-0 pt-3 pb-4 text-xs\">\n          <span className=\"flex items-center gap-1.5\">\n            <Shield className=\"text-success size-3.5\" />\n            Cryptographically random &bull; Never transmitted over network\n          </span>\n          <span className=\"font-mono tabular-nums\">{history.length} entries</span>\n        </CardFooter>\n      </Card>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/PasswordGeneratorWidget.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/separator.json",
    "https://uipkge.dev/r/react/slider.json",
    "https://uipkge.dev/r/react/switch.json",
    "https://uipkge.dev/r/react/tabs.json"
  ],
  "description": "1Password and Bitwarden-style customizable password and passphrase generator with entropy strength meter, character type toggles, memorable Diceware passphrases, PIN codes, and volatile history drawer.",
  "categories": [
    "security",
    "auth",
    "app"
  ]
}