{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "password-generator-widget",
  "title": "Password Generator Widget",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/password-generator-widget/PasswordGeneratorWidget.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, onMounted, ref, watch, type HTMLAttributes } from 'vue'\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-vue-next'\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\ninterface Props {\n  class?: HTMLAttributes['class']\n}\n\ndefineProps<Props>()\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\n// --- STATE ---\nconst mode = ref<'password' | 'passphrase' | 'pin'>('password')\nconst passwordLength = ref(20)\nconst includeUpper = ref(true)\nconst includeLower = ref(true)\nconst includeNumbers = ref(true)\nconst includeSymbols = ref(true)\nconst avoidAmbiguous = ref(true)\n\n// Passphrase state\nconst wordCount = ref(5)\nconst separator = ref('-')\nconst capitalizeWords = ref(true)\nconst includeNumberInPassphrase = ref(true)\n\n// PIN state\nconst pinLength = ref(6)\nconst avoidPinRepeats = ref(true)\n\n// Output state\nconst currentSecret = ref('')\nconst copied = ref(false)\nconst isSpinning = ref(false)\nconst copiedHistoryId = ref<string | null>(null)\nconst revealedHistoryIds = ref<Set<string>>(new Set())\n\n// History\nconst history = ref<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// --- CRYPTO RANDOM HELPER ---\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\n// --- GENERATION FUNCTIONS ---\nfunction generatePassword(): string {\n  let pool = ''\n  const requiredChars: string[] = []\n\n  const filterPool = (chars: string) => {\n    if (!avoidAmbiguous.value) 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.value && uppers.length > 0) {\n    pool += uppers\n    requiredChars.push(uppers[getRandomInt(uppers.length)])\n  }\n  if (includeLower.value && lowers.length > 0) {\n    pool += lowers\n    requiredChars.push(lowers[getRandomInt(lowers.length)])\n  }\n  if (includeNumbers.value && numbers.length > 0) {\n    pool += numbers\n    requiredChars.push(numbers[getRandomInt(numbers.length)])\n  }\n  if (includeSymbols.value && 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.value; 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}\n\nfunction generatePassphrase(): string {\n  const chosenWords: string[] = []\n  for (let i = 0; i < wordCount.value; i++) {\n    let word = WORD_LIST[getRandomInt(WORD_LIST.length)]\n    if (capitalizeWords.value) {\n      word = word.charAt(0).toUpperCase() + word.slice(1)\n    }\n    chosenWords.push(word)\n  }\n\n  if (includeNumberInPassphrase.value) {\n    const num = getRandomInt(90) + 10 // 2-digit number (10-99)\n    chosenWords.push(num.toString())\n  }\n\n  return chosenWords.join(separator.value)\n}\n\nfunction generatePin(): string {\n  const digits: string[] = []\n  for (let i = 0; i < pinLength.value; i++) {\n    let nextDigit = getRandomInt(10).toString()\n    if (avoidPinRepeats.value && 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}\n\nfunction regenerateSecret(recordHistory = true) {\n  let secret = ''\n  if (mode.value === 'password') {\n    secret = generatePassword()\n  } else if (mode.value === 'passphrase') {\n    secret = generatePassphrase()\n  } else {\n    secret = generatePin()\n  }\n\n  currentSecret.value = secret\n\n  if (recordHistory && secret) {\n    const ent = currentStrength.value.entropy\n    const item: HistoryItem = {\n      id: `hist-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,\n      secret,\n      mode: mode.value,\n      entropy: ent,\n      strengthLabel: currentStrength.value.label,\n      createdAt: 'Just now',\n    }\n    // Prepend and limit to 8 items\n    history.value = [item, ...history.value.slice(0, 7)]\n  }\n}\n\nfunction handleManualRegenerate() {\n  isSpinning.value = true\n  regenerateSecret(true)\n  setTimeout(() => {\n    isSpinning.value = false\n  }, 400)\n}\n\n// Ensure at least one character type stays active in password mode\nfunction toggleUpper(val: boolean) {\n  if (!val && !includeLower.value && !includeNumbers.value && !includeSymbols.value) return\n  includeUpper.value = val\n}\n\nfunction toggleLower(val: boolean) {\n  if (!val && !includeUpper.value && !includeNumbers.value && !includeSymbols.value) return\n  includeLower.value = val\n}\n\nfunction toggleNumbers(val: boolean) {\n  if (!val && !includeUpper.value && !includeLower.value && !includeSymbols.value) return\n  includeNumbers.value = val\n}\n\nfunction toggleSymbols(val: boolean) {\n  if (!val && !includeUpper.value && !includeLower.value && !includeNumbers.value) return\n  includeSymbols.value = val\n}\n\n// Watchers for reactive auto-regeneration\nwatch(\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    regenerateSecret(false)\n  },\n  { immediate: false },\n)\n\nonMounted(() => {\n  regenerateSecret(false)\n})\n\n// --- ENTROPY & STRENGTH METRICS ---\nconst currentStrength = computed(() => {\n  let entropy = 0\n  if (mode.value === 'password') {\n    let poolSize = 0\n    if (includeUpper.value) poolSize += avoidAmbiguous.value ? 24 : 26\n    if (includeLower.value) poolSize += avoidAmbiguous.value ? 24 : 26\n    if (includeNumbers.value) poolSize += avoidAmbiguous.value ? 8 : 10\n    if (includeSymbols.value) poolSize += avoidAmbiguous.value ? 26 : 28\n    entropy = poolSize > 1 ? Math.round(passwordLength.value * Math.log2(poolSize)) : 0\n  } else if (mode.value === 'passphrase') {\n    entropy = wordCount.value * Math.log2(WORD_LIST.length)\n    if (capitalizeWords.value) entropy += wordCount.value * 1\n    if (includeNumberInPassphrase.value) entropy += Math.log2(90)\n    entropy = Math.round(entropy)\n  } else {\n    entropy = Math.round(pinLength.value * 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\n// --- CHARACTER FORMATTING FOR SYNTAX HIGHLIGHTING ---\nconst formattedCharacters = computed(() => {\n  if (!currentSecret.value) return []\n  return currentSecret.value.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\nfunction 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 ---\nasync function copySecret(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      copiedHistoryId.value = historyId\n      setTimeout(() => {\n        if (copiedHistoryId.value === historyId) copiedHistoryId.value = null\n      }, 2000)\n    } else {\n      copied.value = true\n      setTimeout(() => {\n        copied.value = false\n      }, 2000)\n    }\n  } catch (err) {\n    console.error('Failed to copy to clipboard', err)\n  }\n}\n\nfunction toggleHistoryReveal(id: string) {\n  if (revealedHistoryIds.value.has(id)) {\n    revealedHistoryIds.value.delete(id)\n  } else {\n    revealedHistoryIds.value.add(id)\n  }\n}\n\nfunction clearHistory() {\n  history.value = []\n}\n</script>\n\n<template>\n  <div data-slot=\"password-generator-widget\" :class=\"cn('mx-auto w-full max-w-4xl space-y-6', $props.class)\">\n    <!-- HERO: GENERATOR OUTPUT CARD -->\n    <Card class=\"border-border overflow-hidden shadow-xs\">\n      <CardHeader class=\"border-border/50 bg-muted/20 border-b pb-3\">\n        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n          <div class=\"flex items-center gap-2\">\n            <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n              <KeyRound class=\"size-4\" />\n            </div>\n            <div>\n              <CardTitle class=\"text-base font-semibold\">Generated Secret</CardTitle>\n              <CardDescription class=\"text-xs\">\n                Client-side cryptographic output &amp; entropy estimation\n              </CardDescription>\n            </div>\n          </div>\n          <div class=\"flex items-center gap-2\">\n            <Badge variant=\"outline\" class=\"gap-1.5 text-xs font-normal\">\n              <span class=\"bg-success size-2 animate-pulse rounded-full\" />\n              100% Client-Side\n            </Badge>\n          </div>\n        </div>\n      </CardHeader>\n\n      <CardContent class=\"space-y-6 pt-6\">\n        <!-- Monospace Output Box -->\n        <div\n          class=\"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        >\n          <div\n            class=\"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          >\n            <template v-if=\"formattedCharacters.length > 0\">\n              <span\n                v-for=\"(item, idx) in formattedCharacters\"\n                :key=\"idx\"\n                :class=\"[\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            </template>\n            <span v-else class=\"text-muted-foreground text-sm\">Generating secret...</span>\n          </div>\n\n          <!-- Quick Action Buttons -->\n          <div class=\"flex shrink-0 items-center gap-2 self-end sm:self-center\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              class=\"h-9 gap-1.5 px-3 active:scale-95\"\n              @click=\"handleManualRegenerate\"\n            >\n              <RefreshCw :class=\"cn('size-4', isSpinning && 'text-primary animate-spin')\" />\n              <span class=\"text-xs font-medium\">Regenerate</span>\n            </Button>\n\n            <Button\n              variant=\"default\"\n              size=\"sm\"\n              :class=\"cn('h-9 gap-1.5 px-3.5 active:scale-95', copied && 'bg-success hover:bg-success text-white')\"\n              @click=\"copySecret(currentSecret)\"\n            >\n              <Check v-if=\"copied\" class=\"size-4 stroke-[2.5]\" />\n              <Copy v-else class=\"size-4\" />\n              <span class=\"text-xs font-medium\">{{ copied ? 'Copied!' : 'Copy' }}</span>\n            </Button>\n          </div>\n        </div>\n\n        <!-- Entropy & Strength Meter -->\n        <div class=\"border-border/60 bg-muted/10 space-y-2.5 rounded-lg border p-4\">\n          <div class=\"flex flex-wrap items-center justify-between gap-2 text-xs\">\n            <div class=\"flex items-center gap-2\">\n              <ShieldCheck class=\"size-4\" :class=\"currentStrength.color\" />\n              <span class=\"text-foreground font-semibold\">{{ currentStrength.label }}</span>\n              <span class=\"text-muted-foreground font-mono\">{{ currentStrength.entropy }}-bit entropy</span>\n            </div>\n            <div class=\"text-muted-foreground flex items-center gap-1.5\">\n              <Clock class=\"size-3.5\" />\n              <span>Crack time:</span>\n              <span class=\"text-foreground font-medium\">{{ currentStrength.crackTime }}</span>\n            </div>\n          </div>\n\n          <!-- 4-Segment Strength Bar -->\n          <div class=\"grid h-2 w-full grid-cols-4 gap-1.5 overflow-hidden rounded-full\">\n            <div\n              class=\"rounded-full transition-colors duration-300\"\n              :class=\"currentStrength.score >= 1 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted'\"\n            />\n            <div\n              class=\"rounded-full transition-colors duration-300\"\n              :class=\"currentStrength.score >= 2 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted'\"\n            />\n            <div\n              class=\"rounded-full transition-colors duration-300\"\n              :class=\"currentStrength.score >= 3 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted'\"\n            />\n            <div\n              class=\"rounded-full transition-colors duration-300\"\n              :class=\"currentStrength.score >= 4 ? currentStrength.barClass : 'bg-muted/80 dark:bg-muted'\"\n            />\n          </div>\n\n          <!-- Monospace Character Type Legend -->\n          <div class=\"text-muted-foreground flex flex-wrap items-center gap-4 pt-1 text-xs\">\n            <span class=\"flex items-center gap-1.5\">\n              <span class=\"bg-foreground size-2 rounded-full\" />\n              Letters\n            </span>\n            <span class=\"flex items-center gap-1.5\">\n              <span class=\"bg-info size-2 rounded-full\" />\n              Numbers (0-9)\n            </span>\n            <span class=\"flex items-center gap-1.5\">\n              <span class=\"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 class=\"border-border shadow-xs\">\n      <CardHeader class=\"border-border/50 border-b pb-3\">\n        <div class=\"flex items-center gap-2\">\n          <div class=\"bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-lg\">\n            <Sliders class=\"size-4\" />\n          </div>\n          <div>\n            <CardTitle class=\"text-base font-semibold\">Generator Configuration</CardTitle>\n            <CardDescription class=\"text-xs\">\n              Customize character pools, length constraints, and format patterns\n            </CardDescription>\n          </div>\n        </div>\n      </CardHeader>\n\n      <CardContent class=\"pt-6\">\n        <Tabs v-model=\"mode\" class=\"w-full\">\n          <TabsList class=\"mb-6 grid w-full grid-cols-3\">\n            <TabsTrigger value=\"password\" class=\"gap-1.5 text-xs\">\n              <Lock class=\"size-3.5\" />\n              <span>Random Password</span>\n            </TabsTrigger>\n            <TabsTrigger value=\"passphrase\" class=\"gap-1.5 text-xs\">\n              <Dice5 class=\"size-3.5\" />\n              <span>Passphrase</span>\n            </TabsTrigger>\n            <TabsTrigger value=\"pin\" class=\"gap-1.5 text-xs\">\n              <Hash class=\"size-3.5\" />\n              <span>PIN Code</span>\n            </TabsTrigger>\n          </TabsList>\n\n          <!-- TAB 1: RANDOM PASSWORD -->\n          <TabsContent value=\"password\" class=\"mt-0 space-y-6\">\n            <!-- Length Control -->\n            <div class=\"space-y-3\">\n              <div class=\"flex items-center justify-between\">\n                <label class=\"text-foreground text-sm font-medium\">Password Length</label>\n                <Badge variant=\"secondary\" class=\"font-mono text-xs font-semibold tabular-nums\">\n                  {{ passwordLength }} characters\n                </Badge>\n              </div>\n\n              <Slider\n                :model-value=\"passwordLength\"\n                :min=\"8\"\n                :max=\"64\"\n                :step=\"1\"\n                class=\"w-full py-1\"\n                @update:model-value=\"(val) => (passwordLength = Array.isArray(val) ? val[0] : val)\"\n              />\n\n              <!-- Preset length buttons -->\n              <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                <span class=\"text-muted-foreground mr-1 text-xs\">Presets:</span>\n                <button\n                  v-for=\"preset in [12, 16, 20, 24, 32, 48, 64]\"\n                  :key=\"preset\"\n                  type=\"button\"\n                  :class=\"\n                    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                  \"\n                  @click=\"passwordLength = preset\"\n                >\n                  {{ preset }}\n                </button>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- Character Type Switches -->\n            <div class=\"space-y-3\">\n              <label class=\"text-muted-foreground text-xs font-medium\"> Character Pools </label>\n\n              <div class=\"border-border/80 bg-muted/20 space-y-3 rounded-lg border p-4\">\n                <!-- Uppercase -->\n                <div class=\"flex items-center justify-between\">\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                      <span>Uppercase Letters</span>\n                      <span class=\"text-muted-foreground font-mono text-xs font-normal\">(A-Z)</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">Include capital Latin characters</p>\n                  </div>\n                  <Switch :model-value=\"includeUpper\" @update:model-value=\"toggleUpper\" />\n                </div>\n\n                <Separator />\n\n                <!-- Lowercase -->\n                <div class=\"flex items-center justify-between\">\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                      <span>Lowercase Letters</span>\n                      <span class=\"text-muted-foreground font-mono text-xs font-normal\">(a-z)</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">Include small Latin characters</p>\n                  </div>\n                  <Switch :model-value=\"includeLower\" @update:model-value=\"toggleLower\" />\n                </div>\n\n                <Separator />\n\n                <!-- Numbers -->\n                <div class=\"flex items-center justify-between\">\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                      <span>Numbers</span>\n                      <span class=\"text-info font-mono text-xs font-semibold\">(0-9)</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">Include numeric digits 0 through 9</p>\n                  </div>\n                  <Switch :model-value=\"includeNumbers\" @update:model-value=\"toggleNumbers\" />\n                </div>\n\n                <Separator />\n\n                <!-- Symbols -->\n                <div class=\"flex items-center justify-between\">\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                      <span>Special Symbols</span>\n                      <span class=\"text-warning font-mono text-xs font-semibold\">(!@#$%^&*)</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">Include non-alphanumeric punctuation marks</p>\n                  </div>\n                  <Switch :model-value=\"includeSymbols\" @update:model-value=\"toggleSymbols\" />\n                </div>\n\n                <Separator />\n\n                <!-- Avoid Ambiguous -->\n                <div class=\"flex items-center justify-between\">\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                      <span>Avoid Ambiguous Characters</span>\n                      <span class=\"text-muted-foreground font-mono text-xs font-normal\">(l, 1, I, O, 0)</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">Exclude confusing lookalike letters and digits</p>\n                  </div>\n                  <Switch :model-value=\"avoidAmbiguous\" @update:model-value=\"(val) => (avoidAmbiguous = val)\" />\n                </div>\n              </div>\n            </div>\n          </TabsContent>\n\n          <!-- TAB 2: MEMORABLE PASSPHRASE -->\n          <TabsContent value=\"passphrase\" class=\"mt-0 space-y-6\">\n            <!-- Word Count Control -->\n            <div class=\"space-y-3\">\n              <div class=\"flex items-center justify-between\">\n                <label class=\"text-foreground text-sm font-medium\">Number of Words</label>\n                <Badge variant=\"secondary\" class=\"font-mono text-xs font-semibold tabular-nums\">\n                  {{ wordCount }} words\n                </Badge>\n              </div>\n\n              <Slider\n                :model-value=\"wordCount\"\n                :min=\"3\"\n                :max=\"8\"\n                :step=\"1\"\n                class=\"w-full py-1\"\n                @update:model-value=\"(val) => (wordCount = Array.isArray(val) ? val[0] : val)\"\n              />\n\n              <!-- Preset word count buttons -->\n              <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                <span class=\"text-muted-foreground mr-1 text-xs\">Presets:</span>\n                <button\n                  v-for=\"preset in [3, 4, 5, 6, 8]\"\n                  :key=\"preset\"\n                  type=\"button\"\n                  :class=\"\n                    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                  \"\n                  @click=\"wordCount = preset\"\n                >\n                  {{ preset }} words\n                </button>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- Separator Selector -->\n            <div class=\"space-y-3\">\n              <label class=\"text-muted-foreground text-xs font-medium\"> Word Separator </label>\n\n              <div class=\"grid grid-cols-5 gap-2\">\n                <button\n                  v-for=\"sep in [\n                    { id: '-', label: 'Hyphen (-)' },\n                    { id: '_', label: 'Under (_)' },\n                    { id: '.', label: 'Period (.)' },\n                    { id: ' ', label: 'Space ( )' },\n                    { id: '/', label: 'Slash (/)' },\n                  ]\"\n                  :key=\"sep.id\"\n                  type=\"button\"\n                  :class=\"\n                    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                  \"\n                  @click=\"separator = sep.id\"\n                >\n                  <span class=\"font-mono text-sm font-semibold\">{{ sep.id === ' ' ? '␣' : sep.id }}</span>\n                  <span class=\"mt-0.5 truncate text-xs\">{{ sep.label.split(' ')[0] }}</span>\n                </button>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- Passphrase Switches -->\n            <div class=\"border-border/80 bg-muted/20 space-y-3 rounded-lg border p-4\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"space-y-0.5\">\n                  <div class=\"text-foreground text-sm font-medium\">Capitalize Words</div>\n                  <p class=\"text-muted-foreground text-xs\">Transform each word into TitleCase for easy reading</p>\n                </div>\n                <Switch :model-value=\"capitalizeWords\" @update:model-value=\"(val) => (capitalizeWords = val)\" />\n              </div>\n\n              <Separator />\n\n              <div class=\"flex items-center justify-between\">\n                <div class=\"space-y-0.5\">\n                  <div class=\"text-foreground text-sm font-medium\">Append Random Number</div>\n                  <p class=\"text-muted-foreground text-xs\">Add a 2-digit number (10-99) for additional security</p>\n                </div>\n                <Switch\n                  :model-value=\"includeNumberInPassphrase\"\n                  @update:model-value=\"(val) => (includeNumberInPassphrase = val)\"\n                />\n              </div>\n            </div>\n          </TabsContent>\n\n          <!-- TAB 3: PIN CODE -->\n          <TabsContent value=\"pin\" class=\"mt-0 space-y-6\">\n            <!-- Length Control -->\n            <div class=\"space-y-3\">\n              <div class=\"flex items-center justify-between\">\n                <label class=\"text-foreground text-sm font-medium\">PIN Length</label>\n                <Badge variant=\"secondary\" class=\"font-mono text-xs font-semibold tabular-nums\">\n                  {{ pinLength }} digits\n                </Badge>\n              </div>\n\n              <Slider\n                :model-value=\"pinLength\"\n                :min=\"4\"\n                :max=\"16\"\n                :step=\"1\"\n                class=\"w-full py-1\"\n                @update:model-value=\"(val) => (pinLength = Array.isArray(val) ? val[0] : val)\"\n              />\n\n              <!-- Preset PIN length buttons -->\n              <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                <span class=\"text-muted-foreground mr-1 text-xs\">Presets:</span>\n                <button\n                  v-for=\"preset in [4, 6, 8, 10, 12, 16]\"\n                  :key=\"preset\"\n                  type=\"button\"\n                  :class=\"\n                    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                  \"\n                  @click=\"pinLength = preset\"\n                >\n                  {{ preset }} digits\n                </button>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- PIN Rules -->\n            <div class=\"border-border/80 bg-muted/20 space-y-3 rounded-lg border p-4\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"space-y-0.5\">\n                  <div class=\"text-foreground text-sm font-medium\">Avoid Consecutive Repeated Digits</div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Prevent obvious repetitive patterns like &ldquo;1122&rdquo; or &ldquo;8888&rdquo;\n                  </p>\n                </div>\n                <Switch :model-value=\"avoidPinRepeats\" @update:model-value=\"(val) => (avoidPinRepeats = val)\" />\n              </div>\n            </div>\n          </TabsContent>\n        </Tabs>\n      </CardContent>\n    </Card>\n\n    <!-- RECENT HISTORY CARD -->\n    <Card class=\"border-border shadow-xs\">\n      <CardHeader class=\"border-border/50 border-b pb-3\">\n        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n          <div class=\"flex items-center gap-2\">\n            <div class=\"bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-lg\">\n              <History class=\"size-4\" />\n            </div>\n            <div>\n              <CardTitle class=\"text-base font-semibold\">Generated History</CardTitle>\n              <CardDescription class=\"text-xs\">\n                Stored in volatile browser memory only &bull; Cleared on page refresh\n              </CardDescription>\n            </div>\n          </div>\n          <Button\n            v-if=\"history.length > 0\"\n            variant=\"ghost\"\n            size=\"sm\"\n            class=\"text-muted-foreground hover:text-destructive h-8 gap-1.5 text-xs\"\n            @click=\"clearHistory\"\n          >\n            <Trash2 class=\"size-3.5\" />\n            <span>Clear History</span>\n          </Button>\n        </div>\n      </CardHeader>\n\n      <CardContent class=\"pt-4\">\n        <div v-if=\"history.length === 0\" class=\"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 v-else class=\"divide-border/60 divide-y\">\n          <div\n            v-for=\"item in history\"\n            :key=\"item.id\"\n            class=\"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 class=\"flex min-w-0 items-center gap-3\">\n              <div class=\"bg-muted text-muted-foreground flex size-7 shrink-0 items-center justify-center rounded-md\">\n                <Lock v-if=\"item.mode === 'password'\" class=\"size-3.5\" />\n                <Dice5 v-else-if=\"item.mode === 'passphrase'\" class=\"size-3.5\" />\n                <Hash v-else class=\"size-3.5\" />\n              </div>\n\n              <div class=\"min-w-0 space-y-1\">\n                <div class=\"flex items-center gap-2\">\n                  <!-- Monospace secret or masked bullets -->\n                  <div class=\"max-w-[18rem] truncate font-mono text-xs font-medium md:max-w-md\">\n                    <template v-if=\"revealedHistoryIds.has(item.id)\">\n                      <span\n                        v-for=\"(subChar, idx) in formatItemCharacters(item.secret)\"\n                        :key=\"idx\"\n                        :class=\"[\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                    </template>\n                    <span v-else class=\"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                  </div>\n\n                  <!-- Reveal Toggle -->\n                  <button\n                    type=\"button\"\n                    class=\"text-muted-foreground hover:text-foreground cursor-pointer p-0.5 transition-colors\"\n                    title=\"Toggle Visibility\"\n                    @click=\"toggleHistoryReveal(item.id)\"\n                  >\n                    <EyeOff v-if=\"revealedHistoryIds.has(item.id)\" class=\"size-3.5\" />\n                    <Eye v-else class=\"size-3.5\" />\n                  </button>\n                </div>\n\n                <div class=\"text-muted-foreground flex items-center gap-2 text-xs\">\n                  <Badge variant=\"outline\" class=\"px-1.5 py-0 text-xs font-normal\">\n                    {{ item.mode }}\n                  </Badge>\n                  <span>&bull;</span>\n                  <span>{{ item.entropy }}-bit ({{ item.strengthLabel }})</span>\n                  <span>&bull;</span>\n                  <span>{{ item.createdAt }}</span>\n                </div>\n              </div>\n            </div>\n\n            <div class=\"flex shrink-0 items-center gap-2 self-end sm:self-center\">\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                :class=\"\n                  cn(\n                    'h-8 gap-1.5 px-2.5 text-xs active:scale-95',\n                    copiedHistoryId === item.id && 'bg-success hover:bg-success text-white',\n                  )\n                \"\n                @click=\"copySecret(item.secret, true, item.id)\"\n              >\n                <Check v-if=\"copiedHistoryId === item.id\" class=\"size-3.5\" />\n                <Copy v-else class=\"size-3.5\" />\n                <span>{{ copiedHistoryId === item.id ? 'Copied' : 'Copy' }}</span>\n              </Button>\n            </div>\n          </div>\n        </div>\n      </CardContent>\n      <CardFooter\n        class=\"text-muted-foreground border-border/40 flex items-center justify-between border-t pt-0 pt-3 pb-4 text-xs\"\n      >\n        <span class=\"flex items-center gap-1.5\">\n          <Shield class=\"text-success size-3.5\" />\n          Cryptographically random &bull; Never transmitted over network\n        </span>\n        <span class=\"font-mono tabular-nums\">{{ history.length }} entries</span>\n      </CardFooter>\n    </Card>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/PasswordGeneratorWidget.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/slider.json",
    "https://uipkge.dev/r/vue/switch.json",
    "https://uipkge.dev/r/vue/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"
  ]
}