{"version":3,"file":"index.mjs","names":["z","z","parseDuration"],"sources":["../src/utils/index.ts","../src/schemas/common.schema.ts","../src/schemas/recipe.schema.ts","../src/exceptions/index.ts","../src/logger.ts","../src/plugin-manager.ts","../src/abstract-postprocessor-plugin.ts","../src/utils/parsing.ts","../src/utils/ingredients.ts","../src/utils/instructions.ts","../src/plugins/html-stripper.processor.ts","../src/plugins/ingredient-parser.processor.ts","../src/abstract-plugin.ts","../src/abstract-extractor-plugin.ts","../src/plugins/opengraph.extractor.ts","../src/utils/json.ts","../src/utils/microdata.ts","../src/utils/parse-yields.ts","../src/plugins/schema-org.extractor/type-predicates.ts","../src/plugins/schema-org.extractor/index.ts","../src/constants.ts","../src/recipe-extractor.ts","../src/schema-adapter.ts","../src/utils/notes.ts","../src/utils/extract-wprm-notes.ts","../src/abstract-scraper.ts","../src/scrapers/americastestkitchen.ts","../src/scrapers/bbcgoodfood.ts","../src/scrapers/bongeats.ts","../src/scrapers/brianlagerstrom.ts","../src/scrapers/damndelicious.ts","../src/scrapers/epicurious.ts","../src/scrapers/inspiredtaste.ts","../src/scrapers/myplate.ts","../src/scrapers/nytimes.ts","../src/scrapers/onceuponachef.ts","../src/scrapers/simplyrecipes.ts","../src/scrapers/skinnytaste.ts","../src/scrapers/theclevercarrot.ts","../src/scrapers/_index.ts","../src/scrapers/generic.ts","../src/index.ts"],"sourcesContent":["export function isDefined<T>(value: T | undefined): value is T {\n  return value !== undefined\n}\n\nexport function isNull<T>(value: T | null): value is null {\n  return value === null\n}\n\n// biome-ignore lint/complexity/noBannedTypes: allowed here\nexport function isFunction(value: unknown): value is Function {\n  return typeof value === 'function'\n}\n\nexport function isNumber(value: unknown): value is number {\n  return typeof value === 'number'\n}\n\nexport function isPlainObject(\n  value: unknown,\n): value is Record<string, unknown> {\n  return (\n    typeof value === 'object' &&\n    value !== null &&\n    Object.getPrototypeOf(value) === Object.prototype\n  )\n}\n\nexport const isObjectLike = (\n  value: unknown,\n): value is Record<PropertyKey, unknown> => {\n  return typeof value === 'object' && value !== null\n}\n\nexport function isString(value: unknown): value is string {\n  return typeof value === 'string'\n}\n\n/**\n * Extracts the host name from a URL string\n * and removes a leading 'www.' prefix if present.\n * Throws an error if the input is not a valid URL.\n */\nexport function getHostName(value: string) {\n  try {\n    const { hostname } = new URL(value)\n    return hostname.startsWith('www.') ? hostname.slice(4) : hostname\n  } catch {\n    throw new Error(`Invalid URL: ${value}`)\n  }\n}\n\n/**\n * Resolves an error message from various error types.\n */\nexport function resolveErrorMessage(\n  error: unknown,\n  defaultMessage = 'Unknown error',\n): string {\n  if (error instanceof Error) {\n    return error.message\n  }\n\n  if (isObjectLike(error) && 'message' in error && isString(error.message)) {\n    return error.message\n  }\n\n  if (isString(error)) {\n    return error\n  }\n\n  return defaultMessage\n}\n","import { z } from 'zod'\n\nconst MAX_STRING_LENGTH = 5000\n\n/**\n * Helper to create a required, non-empty string field\n * Note: Returns the base ZodString so additional methods can be chained\n */\nexport const zString = (fieldName: string, { min = 1, max = 0 } = {}) => {\n  const maxLength = max > 0 ? max : MAX_STRING_LENGTH\n\n  return z\n    .string(`${fieldName} must be a string`)\n    .min(min, `${fieldName} cannot be empty`)\n    .max(maxLength, `${fieldName} must be less than ${maxLength} characters`)\n    .transform((s) => s.trim())\n}\n\n/**\n * Helper to create a URL string field\n */\nexport const zHttpUrl = (fieldName: string) =>\n  z.httpUrl(`${fieldName} must be a valid URL`)\n\n/**\n * Helper to create a positive integer field\n */\nexport const zPositiveInteger = (fieldName: string) =>\n  z\n    .int(`${fieldName} must be an integer`)\n    .positive(`${fieldName} must be positive`)\n    .nullable()\n\nexport const zNonEmptyArray = <T extends z.ZodType>(\n  schema: T,\n  fieldName: string,\n) =>\n  z\n    .array(schema, `${fieldName} items must be an array`)\n    .min(1, `${fieldName} group must have at least one item`)\n","import { z } from 'zod'\nimport { isNull } from '@/utils'\nimport {\n  zHttpUrl,\n  zNonEmptyArray,\n  zPositiveInteger,\n  zString,\n} from './common.schema'\n\n/**\n * Current schema version for recipe objects.\n * Increment this when making breaking changes to the schema.\n *\n * Version history:\n * - 1.0.0: Initial schema version\n */\nexport const RECIPE_SCHEMA_VERSION = '1.0.0' as const\n\n/**\n * Schema for a parsed ingredient from the parse-ingredient library.\n * This represents the structured data extracted from an ingredient string.\n * @see https://github.com/jakeboone02/parse-ingredient\n */\nexport const ParsedIngredientSchema = z.object({\n  /** The primary quantity (the lower quantity in a range, if applicable) */\n  quantity: z.number().nullable(),\n  /** The secondary quantity (the upper quantity in a range, or null if not\n   * applicable) */\n  quantity2: z.number().nullable(),\n  /** The unit of measure identifier (normalized key) */\n  unitOfMeasureID: z.string().nullable(),\n  /** The unit of measure as written in the ingredient string */\n  unitOfMeasure: z.string().nullable(),\n  /** The ingredient description (name of the ingredient) */\n  description: z.string(),\n  /** Whether the \"ingredient\" is actually a group header, e.g. \"For icing:\" */\n  isGroupHeader: z.boolean(),\n})\n\n/**\n * Schema for a single ingredient item\n */\nexport const IngredientItemSchema = z.object({\n  value: zString('Ingredient value'),\n  /**\n   * Parsed ingredient data from the parse-ingredient library.\n   * Only present when parsing is enabled via `parseIngredients` option.\n   */\n  parsed: ParsedIngredientSchema.optional().nullable(),\n})\n\n/**\n * Schema for a group of ingredients\n */\nexport const IngredientGroupSchema = z.object({\n  name: zString('Ingredient group name').nullable(),\n  items: zNonEmptyArray(IngredientItemSchema, 'Ingredient'),\n})\n\n/**\n * Schema for all recipe ingredients\n * Must have at least one group with at least one ingredient\n */\nexport const IngredientsSchema = z\n  .array(IngredientGroupSchema, 'Ingredients must be an array')\n  .min(1, 'Recipe must have at least one ingredient group')\n\n/**\n * Schema for a single instruction step\n */\nexport const InstructionItemSchema = z.object({\n  value: zString('Instruction value'),\n})\n\n/**\n * Schema for a group of instruction steps\n */\nexport const InstructionGroupSchema = z.object({\n  name: zString('Instruction group name').nullable(),\n  items: zNonEmptyArray(InstructionItemSchema, 'Instruction'),\n})\n\n/**\n * Schema for all recipe instructions\n * Must have at least one group with at least one step\n */\nexport const InstructionsSchema = z\n  .array(InstructionGroupSchema, 'Instructions must be an array')\n  .min(1, 'Recipe must have at least one instruction group')\n\n/**\n * Schema for a single recipe note\n */\nexport const NoteItemSchema = z.object({\n  value: zString('Note value'),\n})\n\n/**\n * Schema for a group of recipe notes\n */\nexport const NoteGroupSchema = z.object({\n  name: zString('Note group name').nullable(),\n  items: zNonEmptyArray(NoteItemSchema, 'Note'),\n})\n\n/**\n * Schema for all recipe notes\n * Must have at least one group with at least one note\n */\nexport const NotesSchema = z\n  .array(NoteGroupSchema, 'Notes must be an array')\n  .min(1, 'Recipe must have at least one note group')\n\n/**\n * Schema for a link object\n */\nexport const LinkSchema = z.object({\n  href: zHttpUrl('Link href'),\n  text: zString('Link text'),\n})\n\n/**\n * Base RecipeObject schema without cross-field validations.\n * Use this schema when you need to extend the recipe object with custom fields.\n *\n * @example\n * ```ts\n * import { RecipeObjectBaseSchema, applyRecipeValidations } from 'recipe-scrapers-js'\n *\n * const MyCustomRecipeSchema = RecipeObjectBaseSchema.extend({\n *   customField: z.string(),\n * })\n *\n * // Apply the standard recipe validations\n * const MyValidatedRecipeSchema = applyRecipeValidations(MyCustomRecipeSchema)\n * ```\n */\nexport const RecipeObjectBaseSchema = z.object({\n  // Schema version for migrations\n  schemaVersion: z\n    .literal(RECIPE_SCHEMA_VERSION)\n    .default(RECIPE_SCHEMA_VERSION)\n    .describe('Schema version for recipe data migrations'),\n\n  // Required fields\n  host: z.hostname('Host must be a valid hostname'),\n\n  title: zString('Title', { max: 500 }),\n\n  author: zString('Author', { max: 255 }),\n\n  ingredients: IngredientsSchema,\n  instructions: InstructionsSchema,\n  notes: NotesSchema.optional(),\n\n  // URL fields\n  canonicalUrl: zHttpUrl('Canonical URL'),\n  image: zHttpUrl('Image'),\n\n  // Time fields (in minutes)\n  totalTime: zPositiveInteger('Total time'),\n  cookTime: zPositiveInteger('Cook time'),\n  prepTime: zPositiveInteger('Prep time'),\n\n  // Ratings\n  ratings: z\n    .number('Ratings must be a number')\n    .min(0, 'Ratings must be at least 0')\n    .max(5, 'Ratings must be at most 5')\n    .default(0),\n\n  ratingsCount: z\n    .int('Ratings count must be an integer')\n    .nonnegative('Ratings count must be non-negative')\n    .default(0),\n\n  // String fields\n  yields: zString('Yields'),\n  description: zString('Description'),\n\n  language: zString('Language', { min: 2 }).optional().default('en'),\n\n  siteName: zString('Site name').nullable(),\n\n  cookingMethod: zString('Cooking method').nullable(),\n\n  // List fields\n  category: z\n    .array(zString('Category item'), 'Category must be an array')\n    .default([]),\n\n  cuisine: z\n    .array(zString('Cuisine item'), 'Cuisine must be an array')\n    .default([]),\n\n  keywords: z\n    .array(zString('Keyword item'), 'Keywords must be an array')\n    .default([]),\n\n  dietaryRestrictions: z\n    .array(\n      zString('Dietary restriction item'),\n      'Dietary restrictions must be an array',\n    )\n    .default([]),\n\n  equipment: z\n    .array(zString('Equipment item'), 'Equipment must be an array')\n    .default([]),\n\n  links: z.array(LinkSchema, 'Links must be an array').optional(),\n\n  // Complex fields\n  nutrients: z\n    .record(z.string(), z.string(), 'Nutrients must be an object')\n    .default({}),\n\n  reviews: z\n    .record(z.string(), z.string(), 'Reviews must be an object')\n    .default({}),\n})\n\n/**\n * Applies recipe-specific transformations and validations to a schema.\n * Use this when extending RecipeObjectBaseSchema with custom fields.\n *\n * @param schema - A Zod object schema that includes\n * all RecipeObjectBaseSchema fields\n * @returns A schema with transforms and field validations applied\n *\n * @example\n * ```ts\n * const CustomSchema = RecipeObjectBaseSchema.extend({\n *   tags: z.array(z.string()),\n * })\n *\n * const ValidatedCustomSchema = applyRecipeValidations(CustomSchema)\n * ```\n */\nexport function applyRecipeValidations<\n  T extends z.infer<typeof RecipeObjectBaseSchema>,\n>(schema: z.ZodType<T>) {\n  return schema\n    .transform((data) => {\n      // Auto-fix: calculate totalTime if missing but cook and prep times exist\n      if (!data.totalTime && !isNull(data.cookTime) && !isNull(data.prepTime)) {\n        data.totalTime = data.cookTime + data.prepTime\n      }\n      return data\n    })\n    .refine(\n      ({ totalTime, cookTime, prepTime }) => {\n        if (!isNull(totalTime) && !isNull(cookTime) && !isNull(prepTime)) {\n          return totalTime >= cookTime + prepTime\n        }\n        return true\n      },\n      {\n        message:\n          'Total time should be at least the sum of cook time and prep time',\n        path: ['totalTime'],\n      },\n    )\n    .refine(\n      (data) => {\n        return data.ratings === 0 || data.ratingsCount > 0\n      },\n      {\n        message: 'Ratings count should be greater than 0 when ratings exist',\n        path: ['ratingsCount'],\n      },\n    )\n}\n\n/**\n * Strict RecipeObject schema with all validations enforced.\n * This is the standard schema used by recipe scrapers.\n *\n * For custom extensions, use RecipeObjectBaseSchema.extend() and then\n * apply validations with applyRecipeValidations().\n */\nexport const RecipeObjectSchema = applyRecipeValidations(RecipeObjectBaseSchema)\n","import type { ValidationIssue } from '@/schema-adapter'\nimport { isDefined, resolveErrorMessage } from '@/utils'\n\nexport class ExtractorNotFoundException extends Error {\n  constructor(public readonly field: string) {\n    super(`No extractor found for field: ${field}`)\n    this.name = 'ExtractorNotFoundException'\n  }\n}\n\nexport class NotImplementedException extends Error {\n  constructor(method: string) {\n    super(`Method should be implemented: ${method}`)\n    this.name = 'NotImplementedException'\n  }\n}\n\nexport class UnsupportedFieldException extends Error {\n  constructor(field: string) {\n    super(`Extraction not supported for field: ${field}`)\n    this.name = 'UnsupportedFieldException'\n  }\n}\n\nexport class ExtractionFailedException extends Error {\n  constructor(\n    public readonly field: string,\n    public readonly value?: unknown,\n  ) {\n    const msg = isDefined(value)\n      ? `Invalid value for \"${field}\": ${String(value)}`\n      : `No value found for \"${field}\"`\n\n    super(msg)\n    this.name = 'ExtractionFailedException'\n  }\n}\n\nexport class ExtractionRuntimeException extends Error {\n  constructor(\n    public readonly field: string,\n    public readonly source: string,\n    public readonly extractionCause?: unknown,\n  ) {\n    const causeMessage = resolveErrorMessage(\n      extractionCause,\n      'Unknown extraction error',\n    )\n\n    super(\n      `Unexpected extraction error for field \"${field}\" from ${source}: ${causeMessage}`,\n    )\n    this.name = 'ExtractionRuntimeException'\n  }\n}\n\nexport class NoIngredientsFoundException extends ExtractionFailedException {\n  constructor() {\n    super('ingredients')\n    this.name = 'NoIngredientsFoundException'\n  }\n}\n\nexport class ValidationException extends Error {\n  constructor(\n    public readonly issues: readonly ValidationIssue[],\n    public readonly validationCause?: unknown,\n  ) {\n    super('Recipe validation failed')\n    this.name = 'ValidationException'\n  }\n}\n","export enum LogLevel {\n  VERBOSE = 0,\n  DEBUG = 1,\n  INFO = 2,\n  WARN = 3,\n  ERROR = 4,\n}\n\nexport class Logger {\n  constructor(\n    private context: string,\n    private logLevel = LogLevel.WARN,\n  ) {}\n\n  verbose(...args: unknown[]) {\n    if (this.logLevel > LogLevel.VERBOSE) return\n    console.log(`[VERBOSE][${this.context}]`, ...args)\n  }\n\n  debug(...args: unknown[]) {\n    if (this.logLevel > LogLevel.DEBUG) return\n    console.debug(`[DEBUG][${this.context}]`, ...args)\n  }\n\n  log(...args: unknown[]) {\n    if (this.logLevel > LogLevel.INFO) return\n    console.log(`[INFO][${this.context}]`, ...args)\n  }\n\n  info(...args: unknown[]) {\n    if (this.logLevel > LogLevel.INFO) return\n    console.info(`[INFO][${this.context}]`, ...args)\n  }\n\n  warn(...args: unknown[]) {\n    if (this.logLevel > LogLevel.WARN) return\n    console.warn(`[WARN][${this.context}]`, ...args)\n  }\n\n  error(...args: unknown[]) {\n    // Always log errors regardless of log level\n    // This ensures that critical issues are always reported\n    console.error(`[ERROR][${this.context}]`, ...args)\n  }\n}\n","import type { ExtractorPlugin } from './abstract-extractor-plugin'\nimport type { PostProcessorPlugin } from './abstract-postprocessor-plugin'\n\nexport class PluginManager {\n  private extractorPlugins: ExtractorPlugin[]\n  private postProcessorPlugins: PostProcessorPlugin[]\n\n  constructor(\n    baseExtractors: ExtractorPlugin[],\n    basePostProcessors: PostProcessorPlugin[],\n    extraExtractors: ExtractorPlugin[] = [],\n    extraPostProcessors: PostProcessorPlugin[] = [],\n  ) {\n    // Combine base and extra plugins, then sort by priority\n    // in descending order (higher priority first)\n    this.extractorPlugins = [...baseExtractors, ...extraExtractors].sort(\n      (a, b) => b.priority - a.priority,\n    )\n\n    this.postProcessorPlugins = [\n      ...basePostProcessors,\n      ...extraPostProcessors,\n    ].sort((a, b) => b.priority - a.priority)\n  }\n\n  getExtractors() {\n    return this.extractorPlugins\n  }\n\n  getPostProcessors() {\n    return this.postProcessorPlugins\n  }\n}\n","import type { RecipeFields } from './types/recipe.interface'\n\nexport abstract class PostProcessorPlugin {\n  /** The name of the plugin */\n  abstract name: string\n\n  /** The priority of the plugin */\n  abstract priority: number\n\n  abstract shouldProcess<Key extends keyof RecipeFields>(field: Key): boolean\n\n  abstract process<T>(field: keyof RecipeFields, value: T): T | Promise<T>\n}\n","/*******************************************************************************\n * Utility functions for common parsing tasks\n ******************************************************************************/\nimport { parse as parseDuration, toSeconds } from 'iso8601-duration'\n\nexport function normalizeString(str: string | null | undefined): string {\n  return (\n    str\n      ?.trim()\n      // collapse all whitespace to single spaces\n      .replace(/\\s+/g, ' ')\n      // remove any space(s) immediately before a comma\n      .replace(/\\s+,/g, ',') ?? ''\n  )\n}\n\nexport function stripLeadingBullet(value: string): string {\n  return normalizeString(value.replace(/^[\\u2022\\u25aa*-]\\s*/, ''))\n}\n\nexport function splitToList(\n  value: string,\n  separator: string | RegExp,\n): string[] {\n  if (!value) return []\n\n  const items: string[] = []\n\n  for (const item of value.split(separator)) {\n    const str = normalizeString(item)\n\n    if (str) {\n      items.push(str)\n    }\n  }\n\n  return items\n}\n\nfunction parseHumanDurationMinutes(value: string): number | null {\n  const normalized = normalizeString(value).toLowerCase()\n\n  if (!normalized) {\n    return null\n  }\n\n  const matches = Array.from(\n    normalized.matchAll(\n      /(\\d+(?:\\.\\d+)?)\\s*(days?|d|hours?|hrs?|hr|h|minutes?|mins?|min|m|seconds?|secs?|sec|s)\\b/g,\n    ),\n  )\n\n  if (matches.length === 0) {\n    return null\n  }\n\n  let totalMinutes = 0\n\n  for (const match of matches) {\n    const amount = Number.parseFloat(match[1] ?? '')\n    const unit = match[2] ?? ''\n\n    if (Number.isNaN(amount)) {\n      continue\n    }\n\n    if (/^days?$|^d$/.test(unit)) {\n      totalMinutes += amount * 24 * 60\n      continue\n    }\n\n    if (/^hours?$|^hrs?$|^hr$|^h$/.test(unit)) {\n      totalMinutes += amount * 60\n      continue\n    }\n\n    if (/^minutes?$|^mins?$|^min$|^m$/.test(unit)) {\n      totalMinutes += amount\n      continue\n    }\n\n    if (/^seconds?$|^secs?$|^sec$|^s$/.test(unit)) {\n      totalMinutes += amount / 60\n    }\n  }\n\n  return totalMinutes > 0 ? Math.round(totalMinutes) : 0\n}\n\n/**\n * @TODO Implement [Temporal.Duration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration) once it lands.\n */\nexport function parseMinutes(value: string) {\n  try {\n    const duration = parseDuration(value)\n    const totalSeconds = toSeconds(duration)\n    return Math.round(totalSeconds / 60)\n  } catch (error) {\n    const humanDurationMinutes = parseHumanDurationMinutes(value)\n\n    if (humanDurationMinutes !== null) {\n      return humanDurationMinutes\n    }\n\n    throw error\n  }\n}\n","import type { CheerioAPI } from 'cheerio'\nimport type {\n  IngredientGroup,\n  IngredientItem,\n  Ingredients,\n} from '@/types/recipe.interface'\nimport { isPlainObject, isString } from './index'\nimport { normalizeString } from './parsing'\n\nconst DEFAULT_GROUPING_SELECTORS = {\n  wprm: {\n    headingSelectors: [\n      '.wprm-recipe-ingredient-group h4',\n      '.wprm-recipe-group-name',\n    ],\n    itemSelectors: ['.wprm-recipe-ingredient', '.wprm-recipe-ingredients li'],\n  },\n  tasty: {\n    headingSelectors: [\n      '.tasty-recipes-ingredients-body p strong',\n      '.tasty-recipes-ingredients h4',\n    ],\n    itemSelectors: [\n      '.tasty-recipes-ingredients-body ul li',\n      '.tasty-recipes-ingredients ul li',\n    ],\n  },\n} as const satisfies Record<\n  string,\n  { headingSelectors: string[]; itemSelectors: string[] }\n>\n\n/**\n * Creates an IngredientItem.\n */\nexport function createIngredientItem(value: string): IngredientItem {\n  return { value }\n}\n\n/**\n * Creates an IngredientGroup.\n */\nexport function createIngredientGroup(\n  name: string | null,\n  items: IngredientItem[] = [],\n): IngredientGroup {\n  return { name, items }\n}\n\n/**\n * Type guard to check if value is an IngredientItem.\n */\nexport function isIngredientItem(value: unknown): value is IngredientItem {\n  return isPlainObject(value) && 'value' in value && isString(value.value)\n}\n\n/**\n * Type guard to check if value is an IngredientGroup.\n */\nexport function isIngredientGroup(value: unknown): value is IngredientGroup {\n  return (\n    isPlainObject(value) &&\n    'name' in value &&\n    'items' in value &&\n    Array.isArray(value.items) &&\n    value.items.every(isIngredientItem)\n  )\n}\n\n/**\n * Type guard to check if value is an Ingredients array.\n */\nexport function isIngredients(value: unknown): value is Ingredients {\n  return Array.isArray(value) && value.every(isIngredientGroup)\n}\n\n/**\n * Extracts the flat list of ingredient values from an Ingredients array.\n * Useful when scrapers need to re-group ingredients using HTML structure.\n */\nexport function flattenIngredients(ingredients: Ingredients): string[] {\n  return ingredients.flatMap((group) => group.items.map((item) => item.value))\n}\n\n/**\n * Converts an array of strings to an Ingredients array with a single\n * default group.\n */\nexport function stringsToIngredients(\n  values: string[],\n  groupName: string | null = null,\n): Ingredients {\n  const items = values.map(createIngredientItem)\n  return [createIngredientGroup(groupName, items)]\n}\n\nexport function scoreSentenceSimilarity(first: string, second: string): number {\n  if (first === second) {\n    return 1\n  }\n\n  if (first.length < 2 || second.length < 2) {\n    return 0\n  }\n\n  const bigrams = (s: string) =>\n    new Set(Array.from({ length: s.length - 1 }, (_, i) => s.slice(i, i + 2)))\n\n  const firstBigrams = bigrams(first)\n  const secondBigrams = bigrams(second)\n\n  const intersectionSize = [...firstBigrams].filter((b) =>\n    secondBigrams.has(b),\n  ).length\n\n  return (2 * intersectionSize) / (firstBigrams.size + secondBigrams.size)\n}\n\nexport function bestMatch(testString: string, targetStrings: string[]): string {\n  if (targetStrings.length === 0) {\n    throw new Error('targetStrings cannot be empty')\n  }\n\n  const scores = targetStrings.map((t) =>\n    scoreSentenceSimilarity(testString, t),\n  )\n\n  let bestIndex = 0\n  let bestScore = scores[0]\n\n  for (let i = 1; i < scores.length; i++) {\n    if (scores[i] > bestScore) {\n      bestScore = scores[i]\n      bestIndex = i\n    }\n  }\n\n  return targetStrings[bestIndex]\n}\n\nfunction findSelectors(\n  $: CheerioAPI,\n  initialHeading?: string,\n  initialItem?: string,\n): [string, string] | null {\n  if (initialHeading && initialItem) {\n    // Check if the provided selectors actually exist in the DOM\n    if ($(initialHeading).length && $(initialItem).length) {\n      return [initialHeading, initialItem]\n    }\n    // If custom selectors are provided but not found, return null\n    return null\n  }\n\n  const values = Object.values(DEFAULT_GROUPING_SELECTORS)\n\n  for (const { headingSelectors, itemSelectors } of values) {\n    for (const heading of headingSelectors) {\n      for (const item of itemSelectors) {\n        if ($(heading).length && $(item).length) {\n          return [heading, item]\n        }\n      }\n    }\n  }\n\n  return null\n}\n\n/**\n * Groups ingredients based on the provided selectors.\n * If no selectors are provided, it will try to find the best matching\n * selectors from the default grouping selectors.\n *\n * @param $ Cheerio instance\n * @param ingredientValues Array of ingredient strings to group\n * @param headingSelector Optional custom heading selector\n * @param itemSelector Optional custom item selector\n * @returns Ingredients array with groups\n */\nexport function groupIngredients(\n  $: CheerioAPI,\n  ingredientValues: string[],\n  headingSelector?: string,\n  itemSelector?: string,\n): Ingredients {\n  const selectors = findSelectors($, headingSelector, itemSelector)\n\n  if (!selectors) {\n    return stringsToIngredients(ingredientValues)\n  }\n\n  const [groupNameSelector, ingredientSelector] = selectors\n\n  const foundIngredients = $(ingredientSelector)\n    .toArray()\n    .map((el) => normalizeString($(el).text()))\n    .filter((text) => text.length > 0)\n\n  const uniqueFoundIngredients = new Set(foundIngredients)\n  const uniqueIngredientValues = new Set(\n    ingredientValues.map((value) => normalizeString(value)),\n  )\n\n  // Fall back only when HTML under-covers ingredientValues:\n  // - fewer total non-empty entries, or\n  // - fewer unique normalized entries\n  // Extra HTML entries are allowed (some sites duplicate DOM nodes for layout)\n  if (\n    foundIngredients.length < ingredientValues.length ||\n    uniqueFoundIngredients.size < uniqueIngredientValues.size\n  ) {\n    return stringsToIngredients(ingredientValues)\n  }\n\n  const groupings = new Map<string | null, string[]>()\n  let currentHeading: string | null = null\n\n  // iterate in document order over headings & items\n  const elements = $(`${groupNameSelector}, ${ingredientSelector}`).toArray()\n\n  for (const el of elements) {\n    const $el = $(el)\n\n    if ($el.is(groupNameSelector)) {\n      // it's a heading\n      const headingText = normalizeString($el.text()).replace(/:$/, '')\n      currentHeading = headingText || null\n\n      if (!groupings.has(currentHeading)) {\n        groupings.set(currentHeading, [])\n      }\n    } else if ($el.is(ingredientSelector)) {\n      // it's an ingredient\n      const text = normalizeString($el.text())\n\n      if (!text) {\n        continue\n      }\n\n      const matched = bestMatch(text, ingredientValues)\n      const heading = currentHeading ?? null\n\n      if (!groupings.has(heading)) {\n        groupings.set(heading, [])\n      }\n\n      groupings.get(heading)?.push(matched)\n    }\n  }\n\n  // Convert Map to Ingredients array\n  const result: Ingredients = []\n\n  for (const [name, items] of groupings.entries()) {\n    result.push(createIngredientGroup(name, items.map(createIngredientItem)))\n  }\n\n  const nonEmptyGroups = result.filter((group) => group.items.length > 0)\n\n  if (nonEmptyGroups.length > 0) {\n    return nonEmptyGroups\n  }\n\n  return ingredientValues.length > 0\n    ? stringsToIngredients(ingredientValues)\n    : []\n}\n","import type {\n  InstructionGroup,\n  InstructionItem,\n  Instructions,\n} from '@/types/recipe.interface'\nimport { isPlainObject, isString } from './index'\nimport { normalizeString, splitToList } from './parsing'\n\n/**\n * List of possible headings to remove from instructions.\n */\nconst INSTRUCTION_HEADINGS = [\n  'Preparation',\n  'Directions',\n  'Instructions',\n  'Method',\n  'Steps',\n]\n\n/**\n * Creates an InstructionItem.\n */\nexport function createInstructionItem(value: string): InstructionItem {\n  return { value }\n}\n\n/**\n * Creates an InstructionGroup.\n */\nexport function createInstructionGroup(\n  name: string | null,\n  items: InstructionItem[] = [],\n): InstructionGroup {\n  return { name, items }\n}\n\n/**\n * Type guard to check if value is an InstructionItem.\n */\nexport function isInstructionItem(value: unknown): value is InstructionItem {\n  return isPlainObject(value) && 'value' in value && isString(value.value)\n}\n\n/**\n * Type guard to check if value is an InstructionGroup.\n */\nexport function isInstructionGroup(value: unknown): value is InstructionGroup {\n  return (\n    isPlainObject(value) &&\n    'name' in value &&\n    'items' in value &&\n    Array.isArray(value.items) &&\n    value.items.every(isInstructionItem)\n  )\n}\n\n/**\n * Type guard to check if value is an Instructions array.\n */\nexport function isInstructions(value: unknown): value is Instructions {\n  return Array.isArray(value) && value.every(isInstructionGroup)\n}\n\n/**\n * Extracts the flat list of instruction values from an Instructions array.\n * Useful when scrapers need to re-group instructions.\n */\nexport function flattenInstructions(instructions: Instructions): string[] {\n  return instructions.flatMap((group) => group.items.map((item) => item.value))\n}\n\n/**\n * Converts an array of strings to an Instructions array with a single\n * default group.\n */\nexport function stringsToInstructions(\n  values: string[],\n  groupName: string | null = null,\n): Instructions {\n  const items = values.map(createInstructionItem)\n  return [createInstructionGroup(groupName, items)]\n}\n\n/**\n * Removes any heading from the start of the instructions string.\n */\nexport function removeInstructionHeading(value: string) {\n  for (const heading of INSTRUCTION_HEADINGS) {\n    const regex = new RegExp(`^\\\\s*${heading}\\\\s*:?\\\\s*`, 'i')\n    if (regex.test(value)) {\n      return value.replace(regex, '')\n    }\n  }\n  return value\n}\n\nconst NEW_LINE_REGEX = /\\n\\s*\\n+/\nconst SENTENCE_BOUNDARY_REGEX = /(?<=\\.)\\s+(?=[A-Z])/\n\n/**\n * Splits a recipe instructions string into an array of steps.\n * Removes known headings and trims whitespace.\n */\nexport function splitInstructions(value: string) {\n  if (!value) return []\n\n  const cleaned = removeInstructionHeading(value).trim()\n\n  // Split on double newlines or paragraph breaks\n  let steps = splitToList(cleaned, NEW_LINE_REGEX)\n\n  // If only one step, try splitting on sentence boundaries as fallback\n  if (steps.length === 1) {\n    steps = splitToList(cleaned, SENTENCE_BOUNDARY_REGEX)\n  }\n\n  return steps\n}\n\nconst NUMBERED_STEP_REGEX = /(?:^|\\s)(\\d+)\\.\\s+/g\n\n/**\n * Splits text containing inline numbered steps such as\n * \"1. Heat oil. 2. Add onions.\" into individual instruction strings.\n */\nexport function splitNumberedInstructions(value: string): string[] {\n  const normalized = normalizeString(value)\n  const matches = Array.from(normalized.matchAll(NUMBERED_STEP_REGEX))\n\n  if (matches.length === 0) {\n    return normalized ? [normalized] : []\n  }\n\n  const steps: string[] = []\n  const firstMatch = matches[0]\n\n  if (firstMatch && firstMatch.index > 0) {\n    const prefix = normalizeString(normalized.slice(0, firstMatch.index))\n    if (prefix) {\n      steps.push(prefix)\n    }\n  }\n\n  for (let i = 0; i < matches.length; i++) {\n    const match = matches[i]\n    const nextMatch = matches[i + 1]\n\n    if (!match) {\n      continue\n    }\n\n    const start = match.index + match[0].length\n    const end = nextMatch ? nextMatch.index : normalized.length\n    const step = normalizeString(normalized.slice(start, end))\n\n    if (step) {\n      steps.push(step)\n    }\n  }\n\n  return steps\n}\n","import { load } from 'cheerio'\nimport { PostProcessorPlugin } from '@/abstract-postprocessor-plugin'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport { isString } from '@/utils'\nimport { isIngredients } from '@/utils/ingredients'\nimport { isInstructions } from '@/utils/instructions'\nimport type { Ingredients, Instructions } from '../types/recipe.interface'\n\nexport class HtmlStripperPlugin extends PostProcessorPlugin {\n  name = 'HtmlStripper'\n  priority = 100 // Run early\n\n  private fieldsToProcess: (keyof RecipeFields)[] = [\n    'title',\n    'description',\n    'instructions',\n    'ingredients',\n  ]\n\n  shouldProcess<Key extends keyof RecipeFields>(field: Key): boolean {\n    return this.fieldsToProcess.includes(field)\n  }\n\n  process<T>(field: keyof RecipeFields, value: T): T {\n    if (!this.shouldProcess(field)) {\n      return value\n    }\n\n    if (isString(value)) {\n      return this.stripHtml(value) as T\n    }\n\n    if (field === 'instructions' && isInstructions(value)) {\n      return this.processInstructions(value) as T\n    }\n\n    if (field === 'ingredients' && isIngredients(value)) {\n      return this.processIngredients(value) as T\n    }\n\n    return value\n  }\n\n  private processIngredients(ingredients: Ingredients): Ingredients {\n    return ingredients.map((group) => ({\n      name: group.name === null ? null : this.stripHtml(group.name),\n      items: group.items.map((item) => ({\n        value: this.stripHtml(item.value),\n      })),\n    }))\n  }\n\n  private processInstructions(instructions: Instructions): Instructions {\n    return instructions.map((group) => ({\n      name: group.name === null ? null : this.stripHtml(group.name),\n      items: group.items.map((item) => ({\n        value: this.stripHtml(item.value),\n      })),\n    }))\n  }\n\n  private stripHtml(html: string): string {\n    const $ = load(html, null, false)\n    // Cheerio decodes &nbsp; as non-breaking space (U+00A0),\n    // normalize to regular space\n    return $.root()\n      .text()\n      .replace(/\\u00A0/g, ' ')\n      .trim()\n  }\n}\n","import { type ParseIngredientOptions, parseIngredient } from 'parse-ingredient'\nimport { PostProcessorPlugin } from '@/abstract-postprocessor-plugin'\nimport type {\n  IngredientItem,\n  Ingredients,\n  RecipeFields,\n} from '@/types/recipe.interface'\nimport { isIngredients } from '@/utils/ingredients'\n\n/**\n * Post-processor plugin that parses ingredient strings into structured data.\n * Uses the parse-ingredient library to extract quantity, unit, and description.\n *\n * @see https://github.com/jakeboone02/parse-ingredient\n */\nexport class IngredientParserPlugin extends PostProcessorPlugin {\n  name = 'IngredientParser'\n  priority = 50 // Run after HTML stripping\n\n  constructor(private readonly options: ParseIngredientOptions = {}) {\n    super()\n  }\n\n  shouldProcess<Key extends keyof RecipeFields>(field: Key): boolean {\n    return field === 'ingredients'\n  }\n\n  process<T>(field: keyof RecipeFields, value: T): T {\n    if (!this.shouldProcess(field)) {\n      return value\n    }\n\n    if (isIngredients(value)) {\n      return this.processIngredients(value) as T\n    }\n\n    return value\n  }\n\n  private processIngredients(ingredients: Ingredients): Ingredients {\n    return ingredients.map((group) => ({\n      name: group.name,\n      items: group.items.map((item) => this.parseItem(item)),\n    }))\n  }\n\n  private parseItem(item: IngredientItem): IngredientItem {\n    const parsed = parseIngredient(item.value, this.options)\n\n    // parseIngredient returns an array, we take the first result\n    // since we're parsing one ingredient at a time\n    const parsedIngredient = parsed[0] ?? null\n\n    return {\n      value: item.value,\n      parsed: parsedIngredient,\n    }\n  }\n}\n","import type { CheerioAPI } from 'cheerio'\n\nexport abstract class AbstractPlugin {\n  /** The name of the plugin */\n  abstract name: string\n\n  /** The priority of the plugin */\n  abstract priority: number\n\n  constructor(readonly $: CheerioAPI) {}\n}\n","import { AbstractPlugin } from './abstract-plugin'\nimport type { RecipeFields } from './types/recipe.interface'\n\nexport abstract class ExtractorPlugin extends AbstractPlugin {\n  /** Whether this plugin can extract the given field */\n  abstract supports(field: keyof RecipeFields): boolean\n\n  /**\n   * Extracts the field from the cheerio root.\n   * @param field The field to extract\n   * @returns The extracted field value\n   */\n  abstract extract<Key extends keyof RecipeFields>(\n    field: Key,\n  ): RecipeFields[Key] | Promise<RecipeFields[Key]>\n}\n","import { ExtractorPlugin } from '../abstract-extractor-plugin'\nimport {\n  ExtractionFailedException,\n  ExtractorNotFoundException,\n} from '../exceptions'\nimport type { RecipeFields } from '../types/recipe.interface'\n\nexport class OpenGraphException extends ExtractionFailedException {\n  constructor(name: string) {\n    super(name)\n    this.name = 'OpenGraphException'\n  }\n}\n\nexport class OpenGraphPlugin extends ExtractorPlugin {\n  name = OpenGraphPlugin.name\n  priority = 60\n\n  private extractors: {\n    [K in keyof RecipeFields]?: () => RecipeFields[K]\n  } = {\n    image: this.image.bind(this),\n    siteName: this.siteName.bind(this),\n  }\n\n  supports(field: keyof RecipeFields) {\n    return Object.keys(this.extractors).includes(field)\n  }\n\n  extract<Key extends keyof RecipeFields>(field: Key): RecipeFields[Key] {\n    const extractor = this.extractors[field]\n\n    if (!extractor) {\n      throw new ExtractorNotFoundException(field)\n    }\n\n    return extractor()\n  }\n\n  private siteName() {\n    const meta =\n      this.$('meta[property=\"og:site_name\"]').attr('content') ||\n      this.$('meta[name=\"og:site_name\"]').attr('content')\n\n    if (!meta) {\n      throw new OpenGraphException('siteName')\n    }\n\n    return meta\n  }\n\n  private image() {\n    const image = this.$('meta[property=\"og:image\"][content]').attr('content')\n\n    if (!image?.startsWith('http')) {\n      throw new OpenGraphException('image')\n    }\n\n    return image\n  }\n}\n","export interface JsonParseResult {\n  data: unknown\n  repaired: boolean\n}\n\nfunction escapeControlCharacter(value: string): string {\n  switch (value) {\n    case '\\b':\n      return '\\\\b'\n    case '\\f':\n      return '\\\\f'\n    case '\\n':\n      return '\\\\n'\n    case '\\r':\n      return '\\\\r'\n    case '\\t':\n      return '\\\\t'\n    default: {\n      const code = value.charCodeAt(0).toString(16).toUpperCase()\n      return `\\\\u${code.padStart(4, '0')}`\n    }\n  }\n}\n\n/**\n * Escapes raw control characters within JSON string literals.\n *\n * This is a focused repair pass intended for malformed JSON-LD where values\n * contain unescaped newlines/tabs or other ASCII control chars.\n */\nexport function repairJsonControlCharactersInStrings(raw: string): string {\n  let repaired = ''\n  let inString = false\n  let isEscaping = false\n\n  for (const char of raw) {\n    if (!inString) {\n      repaired += char\n\n      if (char === '\"') {\n        inString = true\n      }\n      continue\n    }\n\n    if (isEscaping) {\n      repaired += char\n      isEscaping = false\n      continue\n    }\n\n    if (char === '\\\\') {\n      repaired += char\n      isEscaping = true\n      continue\n    }\n\n    if (char === '\"') {\n      repaired += char\n      inString = false\n      continue\n    }\n\n    if (char.charCodeAt(0) <= 0x1f) {\n      repaired += escapeControlCharacter(char)\n      continue\n    }\n\n    repaired += char\n  }\n\n  return repaired\n}\n\n/**\n * Parses JSON and attempts a control-character repair pass if initial parsing\n * fails.\n * Throws the original parse error if repair is not applicable/successful.\n */\nexport function parseJsonWithRepair(raw: string): JsonParseResult {\n  try {\n    return { data: JSON.parse(raw), repaired: false }\n  } catch (error) {\n    const repairedRaw = repairJsonControlCharactersInStrings(raw)\n\n    if (repairedRaw === raw) {\n      throw error\n    }\n\n    try {\n      return { data: JSON.parse(repairedRaw), repaired: true }\n    } catch {\n      throw error\n    }\n  }\n}\n","import type { Cheerio, CheerioAPI } from 'cheerio'\nimport type { Element } from 'domhandler'\n\ninterface MicrodataObject {\n  '@type'?: string\n  [key: string]: unknown\n}\n\nconst addProperty = (\n  obj: Record<string, unknown>,\n  key: string,\n  value: unknown,\n) => {\n  if (obj[key] === undefined) {\n    obj[key] = value\n  } else if (Array.isArray(obj[key])) {\n    obj[key].push(value)\n  } else {\n    obj[key] = [obj[key], value]\n  }\n}\n\nconst extractValueFromElement = <T extends Element>(\n  element: Cheerio<T>,\n): string | undefined => {\n  if (element.is('meta')) {\n    return element.attr('content')\n  }\n\n  if (element.is('time')) {\n    return element.attr('datetime') || element.text().trim()\n  }\n\n  if (element.is('img')) {\n    return element.attr('src')\n  }\n\n  if (element.is('a')) {\n    return element.attr('href')\n  }\n\n  return element.text().trim()\n}\n\nconst extractSchemaType = (itemType: string): string | undefined => {\n  const typeMatch = itemType.match(/schema\\.org\\/(\\w+)/)\n  return typeMatch?.[1]\n}\n\n/**\n * Extracts microdata from HTML elements using itemtype and itemprop attributes\n *\n * @param $ - Cheerio instance\n * @param selector - Selector to find elements with microdata\n * @returns Array of extracted microdata objects\n */\nexport function extractMicrodata(\n  $: CheerioAPI,\n  selector: string,\n): MicrodataObject[] {\n  const results: MicrodataObject[] = []\n  const elements = $(selector)\n\n  elements.each((_, el) => {\n    const $element = $(el)\n    const itemType = $element.attr('itemtype')\n    const rootObject: MicrodataObject = {}\n\n    // Set the schema type if available\n    if (itemType) {\n      const schemaType = extractSchemaType(itemType)\n      if (schemaType) {\n        rootObject['@type'] = schemaType\n      }\n    }\n\n    // Also get itemprop elements that are not inside nested itemtype elements\n    const allProps = $element.find('[itemprop]').addBack('[itemprop]')\n    const nestedItemTypes = $element.find('[itemtype]')\n\n    // Filter out properties that are inside nested itemtype elements\n    const rootLevelProps = allProps.filter((_, propEl) => {\n      const $prop = $(propEl as Element)\n\n      // If this element itself has itemtype, it's a nested object\n      if ($prop.attr('itemtype')) {\n        return true\n      }\n\n      // Check if this property is inside any nested itemtype element\n      const isInsideNestedType = nestedItemTypes\n        .toArray()\n        .some((nestedEl) => $(nestedEl as Element).find($prop).length > 0)\n\n      return !isInsideNestedType\n    })\n\n    rootLevelProps.each((_, propEl) => {\n      const $prop = $(propEl as Element)\n      const propName = $prop.attr('itemprop')\n      if (!propName) return\n\n      let propValue: string | MicrodataObject | undefined\n\n      // Check if this element has an itemtype (nested object)\n      const nestedItemType = $prop.attr('itemtype')\n      if (nestedItemType) {\n        const nestedObject: MicrodataObject = {}\n\n        // Set nested schema type\n        const nestedSchemaType = extractSchemaType(nestedItemType)\n        if (nestedSchemaType) {\n          nestedObject['@type'] = nestedSchemaType\n        }\n\n        // Extract properties from nested object (only direct children)\n        $prop.find('[itemprop]').each((_, nestedEl) => {\n          const $nested = $(nestedEl as Element)\n          const nestedProp = $nested.attr('itemprop')\n          if (!nestedProp) return\n\n          const nestedValue = extractValueFromElement($nested)\n          if (nestedValue && nestedValue !== '') {\n            addProperty(nestedObject, nestedProp, nestedValue)\n          }\n        })\n\n        propValue = nestedObject\n      } else {\n        // Handle simple property values\n        propValue = extractValueFromElement($prop)\n      }\n\n      if (propValue !== undefined && propValue !== '') {\n        addProperty(rootObject, propName, propValue)\n      }\n    })\n\n    // Only add objects that have properties beyond just @type\n    if (\n      Object.keys(rootObject).length > 1 ||\n      (Object.keys(rootObject).length === 1 && !rootObject['@type'])\n    ) {\n      results.push(rootObject)\n    }\n  })\n\n  return results\n}\n\n/**\n * Extracts Recipe microdata specifically\n *\n * @param $ - Cheerio instance\n * @returns Array of recipe microdata objects\n */\nexport function extractRecipeMicrodata($: CheerioAPI): MicrodataObject[] {\n  return extractMicrodata(\n    $,\n    '[itemtype*=\"schema.org/Recipe\"], [itemtype*=\"Recipe\"]',\n  )\n}\n","const SERVE_REGEX_NUMBER = /(?:\\D*(?<items>\\d+(?:\\.\\d*)?)\\D*)/\n\nconst SERVE_REGEX_ITEMS =\n  /\\bsandwiches\\b|\\btacquitos\\b|\\bmakes\\b|\\bcups\\b|\\bappetizer\\b|\\bporzioni\\b|\\bcookies\\b|\\b(large |small )?buns\\b/i\n\nconst RECIPE_YIELD_TYPES: [string, string][] = [\n  ['dozen', 'dozen'],\n  ['batch', 'batches'],\n  ['cake', 'cakes'],\n  ['sandwich', 'sandwiches'],\n  ['bun', 'buns'],\n  ['cookie', 'cookies'],\n  ['muffin', 'muffins'],\n  ['cupcake', 'cupcakes'],\n  ['loaf', 'loaves'],\n  ['pie', 'pies'],\n  ['cup', 'cups'],\n  ['pint', 'pints'],\n  ['gallon', 'gallons'],\n  ['ounce', 'ounces'],\n  ['pound', 'pounds'],\n  ['gram', 'grams'],\n  ['liter', 'liters'],\n  ['piece', 'pieces'],\n  ['layer', 'layers'],\n  ['scoop', 'scoops'],\n  ['bar', 'bars'],\n  ['patty', 'patties'],\n  ['hamburger bun', 'hamburger buns'],\n  ['pancake', 'pancakes'],\n  ['item', 'items'],\n  // ... add more types as needed, in [singular, plural] format ...\n]\n\n/**\n * Returns a string of servings or items. If the recipe is for a number of\n * items (not servings), it returns \"x item(s)\" where x is the quantity.\n * This function handles cases where the yield is in dozens,\n * such as \"4 dozen cookies\", returning \"4 dozen\" instead of \"4 servings\".\n * Additionally accommodates yields specified in batches\n * (e.g., \"2 batches of brownies\"), returning the yield as stated.\n *\n * @param value The yield string from the recipe\n * @returns The number of servings, items, dozen, batches, etc...\n */\nexport function parseYields(element: string): string {\n  if (!element) {\n    throw new Error('Element is required')\n  }\n\n  const serveText = element\n\n  const match = serveText.match(SERVE_REGEX_NUMBER)\n  const matched = match?.groups?.items || '0'\n\n  const serveTextLower = serveText.toLowerCase()\n  let bestMatch: string | null = null\n  let bestMatchLength = 0\n\n  for (const [singular, plural] of RECIPE_YIELD_TYPES) {\n    if (serveTextLower.includes(singular) || serveTextLower.includes(plural)) {\n      const matchLength = serveTextLower.includes(singular)\n        ? singular.length\n        : plural.length\n      if (matchLength > bestMatchLength) {\n        bestMatchLength = matchLength\n        bestMatch = `${matched} ${Number.parseFloat(matched) === 1 ? singular : plural}`\n      }\n    }\n  }\n\n  // If we found the best match (e.g. \"5 cups\"), append any trailing\n  // parentheses text. That way \"5 cups (about 120...)\" stays intact.\n  if (bestMatch) {\n    const parenMatch = serveText.match(/\\(.*\\)/)\n\n    if (parenMatch) {\n      // e.g. \"5 cups (about 120 to 160 crackers)\"\n      bestMatch += ` ${parenMatch[0]}`\n    }\n    return bestMatch\n  }\n\n  const plural =\n    Number.parseFloat(matched) > 1 || Number.parseFloat(matched) === 0\n      ? 's'\n      : ''\n\n  if (SERVE_REGEX_ITEMS.test(serveText)) {\n    return `${matched} item${plural}`\n  }\n\n  return `${matched} serving${plural}`\n}\n","import type {\n  AggregateRating,\n  HowToSection,\n  HowToStep,\n  Organization,\n  RestrictedDiet,\n  WebPage,\n  WebSite,\n} from 'schema-dts'\nimport { isPlainObject, isString } from '@/utils'\nimport type { Graph, Person, Recipe, Thing } from './schema-org.interface'\n\nexport function hasId(obj: Thing): obj is Thing & { '@id': string } {\n  return '@id' in obj && typeof obj['@id'] === 'string'\n}\n\n// Type guards for runtime type checking\nexport function isGraphType(obj: unknown): obj is Graph {\n  return isPlainObject(obj) && '@graph' in obj && Array.isArray(obj['@graph'])\n}\n\nexport function isBaseType(obj: unknown): obj is { '@type': string } {\n  return (\n    isPlainObject(obj) &&\n    '@type' in obj &&\n    (isString(obj['@type']) || Array.isArray(obj['@type']))\n  )\n}\n\nexport function isSchemaOrgData(obj: unknown): obj is Graph | Thing {\n  return isGraphType(obj) || isBaseType(obj)\n}\n\nexport function isThingType<T extends Thing>(\n  obj: unknown,\n  type: string,\n): obj is Exclude<T, 'string'> {\n  if (!isBaseType(obj)) return false\n\n  const thingType = Array.isArray(obj['@type']) ? obj['@type'][0] : obj['@type']\n\n  return thingType === type\n}\n\nexport function isAggregateRating(obj: unknown): obj is AggregateRating {\n  return isThingType(obj, 'AggregateRating')\n}\n\nexport function isHowToSection(obj: unknown): obj is HowToSection {\n  return isThingType(obj, 'HowToSection')\n}\n\nexport function isHowToStep(obj: unknown): obj is HowToStep {\n  return isThingType(obj, 'HowToStep')\n}\n\nexport function isOrganization(obj: unknown): obj is Organization {\n  return isThingType(obj, 'Organization')\n}\n\nexport function isPerson(obj: unknown): obj is Person {\n  return isThingType(obj, 'Person')\n}\n\nexport function isRecipe(obj: unknown): obj is Recipe {\n  return isThingType(obj, 'Recipe')\n}\n\nexport function isRestrictedDiet(obj: unknown): obj is RestrictedDiet {\n  return isThingType(obj, 'RestrictedDiet')\n}\n\nexport function isWebPage(obj: unknown): obj is WebPage {\n  return isThingType(obj, 'WebPage')\n}\n\nexport function isWebSite(obj: unknown): obj is WebSite {\n  return isThingType(obj, 'WebSite')\n}\n","import type { CheerioAPI } from 'cheerio'\nimport type { AggregateRating } from 'schema-dts'\nimport { ExtractorPlugin } from '@/abstract-extractor-plugin'\nimport {\n  ExtractionFailedException,\n  UnsupportedFieldException,\n} from '@/exceptions'\nimport { Logger, type LogLevel } from '@/logger'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport {\n  isFunction,\n  isNumber,\n  isPlainObject,\n  isString,\n  resolveErrorMessage,\n} from '@/utils'\nimport { groupIngredients } from '@/utils/ingredients'\nimport {\n  createInstructionGroup,\n  createInstructionItem,\n  splitInstructions,\n} from '@/utils/instructions'\nimport { parseJsonWithRepair } from '@/utils/json'\nimport { extractRecipeMicrodata } from '@/utils/microdata'\nimport { parseYields } from '@/utils/parse-yields'\nimport { normalizeString, parseMinutes, splitToList } from '@/utils/parsing'\nimport type {\n  Person,\n  SchemaOrgData,\n  Recipe as SchemaRecipe,\n  Thing,\n} from './schema-org.interface'\nimport {\n  isAggregateRating,\n  isBaseType,\n  isGraphType,\n  isHowToSection,\n  isHowToStep,\n  isOrganization,\n  isPerson,\n  isRecipe,\n  isSchemaOrgData,\n  isThingType,\n  isWebPage,\n  isWebSite,\n} from './type-predicates'\n\nexport class SchemaOrgException extends ExtractionFailedException {\n  constructor(field: string, value?: unknown) {\n    super(field, value)\n    this.name = 'SchemaOrgException'\n  }\n}\n\nexport class SchemaOrgJsonLdParseException extends Error {\n  constructor(\n    public readonly field: keyof RecipeFields,\n    public readonly parseErrors: readonly unknown[],\n  ) {\n    const firstError = parseErrors[0]\n    const parseMessage = resolveErrorMessage(\n      firstError,\n      'Failed to parse JSON-LD',\n    )\n\n    super(\n      `Failed to parse JSON-LD while extracting \"${field}\": ${parseMessage}`,\n    )\n    this.name = 'SchemaOrgJsonLdParseException'\n  }\n}\n\nexport class SchemaOrgPlugin extends ExtractorPlugin {\n  name = SchemaOrgPlugin.name\n\n  // High priority - structured data is very reliable\n  priority = 90\n\n  private logger: Logger\n  private schemaData: SchemaOrgData[] = []\n  private recipe: SchemaRecipe = { '@type': 'Recipe' }\n  private people: Record<string, Person> = {}\n  private ratingsData: Record<string, AggregateRating> = {}\n  private websiteName: string | null = null\n  private jsonLdParseErrors: unknown[] = []\n  private hasRecipeEntity = false\n\n  private extractors: {\n    [K in keyof RecipeFields]?: () => RecipeFields[K]\n  } = {\n    siteName: this.siteName.bind(this),\n    language: this.language.bind(this),\n    title: this.title.bind(this),\n    author: this.author.bind(this),\n    description: this.description.bind(this),\n    image: this.image.bind(this),\n    ingredients: this.ingredients.bind(this),\n    instructions: this.instructions.bind(this),\n    category: this.category.bind(this),\n    yields: this.yields.bind(this),\n    totalTime: this.totalTime.bind(this),\n    cookTime: this.cookTime.bind(this),\n    prepTime: this.prepTime.bind(this),\n    cuisine: this.cuisine.bind(this),\n    cookingMethod: this.cookingMethod.bind(this),\n    ratings: this.ratings.bind(this),\n    ratingsCount: this.ratingsCount.bind(this),\n    nutrients: this.nutrients.bind(this),\n    keywords: this.keywords.bind(this),\n    dietaryRestrictions: this.dietaryRestrictions.bind(this),\n  }\n\n  constructor($: CheerioAPI, logLevel?: LogLevel) {\n    super($)\n\n    this.logger = new Logger(SchemaOrgPlugin.name, logLevel)\n    this.extractJsonLdData()\n    this.extractMicrodataData()\n    this.processSchemaData()\n  }\n\n  supports(field: keyof RecipeFields): boolean {\n    return Object.keys(this.extractors).includes(field)\n  }\n\n  extract<Key extends keyof RecipeFields>(field: Key): RecipeFields[Key] {\n    const extractor = this.extractors[field]\n\n    if (!isFunction(extractor)) {\n      throw new UnsupportedFieldException(field)\n    }\n\n    try {\n      return extractor()\n    } catch (error) {\n      if (\n        error instanceof SchemaOrgException &&\n        this.shouldThrowJsonLdParseException(field)\n      ) {\n        throw new SchemaOrgJsonLdParseException(field, this.jsonLdParseErrors)\n      }\n\n      throw error\n    }\n  }\n\n  /**\n   * Extracts structured JSON-LD data from the page.\n   */\n  private extractJsonLdData() {\n    this.$('script[type=\"application/ld+json\"]').each((_, el) => {\n      try {\n        const json = this.$(el).html()?.trim()\n\n        if (json) {\n          const { data, repaired } = parseJsonWithRepair(json)\n\n          if (repaired) {\n            this.logger.debug(\n              'Recovered malformed JSON-LD by escaping control characters in string values',\n            )\n          }\n\n          if (Array.isArray(data)) {\n            for (const item of data) {\n              if (isSchemaOrgData(item)) {\n                this.schemaData.push(item)\n              }\n            }\n          } else if (isSchemaOrgData(data)) {\n            this.schemaData.push(data)\n          }\n        }\n      } catch (error) {\n        this.logger.warn('Failed to parse JSON-LD', error)\n        this.jsonLdParseErrors.push(error)\n      }\n    })\n  }\n\n  /**\n   * Extracts microdata from the page.\n   */\n  private extractMicrodataData() {\n    const microdataObjects = extractRecipeMicrodata(this.$)\n\n    for (const obj of microdataObjects) {\n      this.schemaData.push(obj as SchemaOrgData)\n    }\n  }\n\n  private pickFromObject(obj: unknown, props: string[]): string | undefined {\n    if (!isPlainObject(obj)) return undefined\n\n    for (const prop of props) {\n      if (isString(obj[prop])) {\n        return obj[prop]\n      }\n    }\n\n    return undefined\n  }\n\n  private getSchemaTextValue<T>(\n    value: unknown,\n    props: string[] = ['textValue', 'name', 'title', '@id'],\n  ): string {\n    let text: string | undefined\n\n    if (isString(value)) {\n      text = value\n    } else if (isNumber(value)) {\n      text = value.toString()\n    } else if (Array.isArray(value)) {\n      text = this.getSchemaTextValue<T>(value[0], props)\n    } else {\n      text = this.pickFromObject(value, props)\n    }\n\n    return normalizeString(text)\n  }\n\n  private schemaValueToList(value: unknown) {\n    let list: string[] = []\n\n    if (Array.isArray(value)) {\n      for (const item of value) {\n        const itemValue = this.getSchemaTextValue(item)\n\n        if (itemValue) {\n          list.push(itemValue)\n        }\n      }\n    } else if (isString(value)) {\n      list = splitToList(this.getSchemaTextValue(value), ',')\n    }\n\n    return new Set(list)\n  }\n\n  private findEntity<T extends Thing>(\n    item: SchemaOrgData,\n    schemaType: string,\n  ): T | null {\n    if (isThingType<T>(item, schemaType)) {\n      return item\n    }\n\n    if (isGraphType(item)) {\n      for (const graphItem of item['@graph']) {\n        if (isThingType<T>(graphItem, schemaType)) {\n          return graphItem\n        }\n      }\n    }\n\n    return null\n  }\n\n  private getIdOrUrl(value: unknown): string | null {\n    if (!isPlainObject(value)) {\n      return null\n    }\n\n    if (isString(value['@id'])) {\n      return value['@id']\n    }\n\n    if (isString(value.url)) {\n      return value.url\n    }\n\n    return null\n  }\n\n  private processSchemaData() {\n    for (const item of this.schemaData) {\n      if (isGraphType(item)) {\n        for (const graphItem of item['@graph']) {\n          this.processSchemaItem(graphItem)\n        }\n      } else {\n        this.processSchemaItem(item)\n      }\n    }\n  }\n\n  private processSchemaItem(obj: Thing) {\n    if (isRecipe(obj)) {\n      return this.processRecipe(obj)\n    }\n\n    return this.processNonRecipeThing(obj)\n  }\n\n  private processRecipe(obj: SchemaRecipe) {\n    this.recipe = { ...this.recipe, ...obj }\n    this.hasRecipeEntity = true\n  }\n\n  private shouldThrowJsonLdParseException(field: keyof RecipeFields): boolean {\n    if (this.jsonLdParseErrors.length === 0) {\n      return false\n    }\n\n    // siteName can come from WebSite schema without a Recipe entity.\n    if (field === 'siteName') {\n      return false\n    }\n\n    return !this.hasRecipeEntity\n  }\n\n  private processNonRecipeThing(obj: Thing) {\n    // Extract website info\n    if (isWebSite(obj)) {\n      this.websiteName = this.getSchemaTextValue(obj)\n    }\n\n    if (isWebPage(obj) && isBaseType(obj.mainEntity)) {\n      this.processSchemaItem(obj.mainEntity)\n    }\n\n    // Extract person info\n    if (isPerson(obj)) {\n      const key = this.getIdOrUrl(obj)\n      if (key) {\n        this.people[key] = obj\n      }\n    }\n\n    // Extract rating info\n    if (isAggregateRating(obj)) {\n      const key = obj['@id']\n      if (key) {\n        this.ratingsData[key] = obj\n      }\n    }\n  }\n\n  private parseDurationField(key: keyof SchemaRecipe): number | null {\n    const value = this.recipe[key]\n\n    if (!value) return null\n\n    if (isNumber(value)) {\n      this.logger.warn(`Duration field \"${key}\" is a number: ${value}`)\n      return value\n    }\n\n    if (isString(value)) {\n      return parseMinutes(value)\n    }\n\n    // Handle QuantitativeValue objects\n    if (isBaseType(value) && 'maxValue' in value) {\n      const maxValue = this.getSchemaTextValue(value.maxValue)\n      return parseMinutes(maxValue)\n    }\n\n    return null\n  }\n\n  private parseInstructions(value: unknown): RecipeFields['instructions'] {\n    if (isString(value)) {\n      const steps = splitInstructions(value)\n      return [createInstructionGroup(null, steps.map(createInstructionItem))]\n    }\n\n    const instructions: unknown[] = Array.isArray(value)\n      ? value.flat()\n      : [value].flat()\n\n    const groups: RecipeFields['instructions'] = []\n\n    let currentGroup: { name: string | null; items: string[] } = {\n      name: null,\n      items: [],\n    }\n\n    for (const item of instructions) {\n      const name = this.getSchemaTextValue(item, ['name'])\n      const text = this.getSchemaTextValue(item, ['text'])\n\n      if (isString(item)) {\n        currentGroup.items.push(normalizeString(item))\n      } else if (isHowToStep(item)) {\n        if (name && text && !text.startsWith(name.replace(/\\.$/, ''))) {\n          currentGroup.items.push(name)\n        }\n\n        if (text) {\n          currentGroup.items.push(text)\n        }\n      } else if (isHowToSection(item)) {\n        // Save current group if it has items\n        if (currentGroup.items.length > 0) {\n          groups.push(\n            createInstructionGroup(\n              currentGroup.name,\n              currentGroup.items.filter(Boolean).map(createInstructionItem),\n            ),\n          )\n        }\n\n        // Start new group with section name\n        currentGroup = { name: name || null, items: [] }\n\n        if (item.itemListElement) {\n          const nestedResult = this.parseInstructions(item.itemListElement)\n          // Merge nested items into current group\n          for (const nestedGroup of nestedResult) {\n            currentGroup.items.push(...nestedGroup.items.map((i) => i.value))\n          }\n        }\n      } else if (text) {\n        currentGroup.items.push(text)\n      }\n    }\n\n    // Add final group if it has items\n    if (currentGroup.items.length > 0) {\n      groups.push(\n        createInstructionGroup(\n          currentGroup.name,\n          currentGroup.items.filter(Boolean).map(createInstructionItem),\n        ),\n      )\n    }\n\n    return groups\n  }\n\n  /*****************************************************************************\n   * Extractor methods\n   ****************************************************************************/\n\n  private siteName(): RecipeFields['siteName'] {\n    if (isOrganization(this.recipe.publisher)) {\n      const publisherName = this.getSchemaTextValue(this.recipe.publisher, [\n        'name',\n        'alternateName',\n      ])\n\n      if (publisherName) {\n        return publisherName\n      }\n    }\n\n    if (!this.websiteName) {\n      throw new SchemaOrgException('siteName')\n    }\n\n    return this.websiteName\n  }\n\n  public language(): RecipeFields['language'] {\n    const language = this.getSchemaTextValue(this.recipe.inLanguage)\n\n    if (!language) {\n      throw new SchemaOrgException('language')\n    }\n\n    return language\n  }\n\n  public title(): RecipeFields['title'] {\n    const title = this.getSchemaTextValue(this.recipe.name)\n\n    if (!title) {\n      throw new SchemaOrgException('title')\n    }\n\n    return title\n  }\n\n  public author(): RecipeFields['author'] {\n    let author = this.recipe.author\n\n    if (Array.isArray(author) && author.length > 0) {\n      author = author[0]\n    }\n\n    const key = this.getIdOrUrl(author)\n\n    if (key && this.people[key]) {\n      author = this.people[key]\n    }\n\n    const authorName = this.getSchemaTextValue(author, ['name'])\n\n    if (!authorName) {\n      throw new SchemaOrgException('author')\n    }\n\n    return authorName\n  }\n\n  public description(): RecipeFields['description'] {\n    const desc = this.getSchemaTextValue(this.recipe.description)\n\n    if (!desc) {\n      throw new SchemaOrgException('description')\n    }\n\n    return desc\n  }\n\n  public image(): RecipeFields['image'] {\n    const image = this.getSchemaTextValue(this.recipe.image, [\n      'url',\n      'contentUrl',\n    ])\n\n    if (!image.startsWith('http')) {\n      throw new SchemaOrgException('image', image)\n    }\n\n    return image\n  }\n\n  public ingredients(): RecipeFields['ingredients'] {\n    const ingredients =\n      this.recipe.recipeIngredient ?? this.recipe.ingredients ?? []\n\n    if (!Array.isArray(ingredients)) {\n      throw new SchemaOrgException('ingredients', ingredients)\n    }\n\n    const flatIngredients = ingredients.flat()\n\n    const uniqueIngredients = new Set<string>()\n\n    for (const item of flatIngredients) {\n      const ingredient = this.getSchemaTextValue(item)\n\n      if (ingredient) {\n        uniqueIngredients.add(ingredient)\n      }\n    }\n\n    if (uniqueIngredients.size === 0) {\n      throw new SchemaOrgException('ingredients', ingredients)\n    }\n\n    return groupIngredients(this.$, [...uniqueIngredients])\n  }\n\n  public instructions(): RecipeFields['instructions'] {\n    const instructions = this.parseInstructions(this.recipe.recipeInstructions)\n\n    if (instructions.length === 0) {\n      throw new SchemaOrgException('instructions')\n    }\n\n    return instructions\n  }\n\n  public category(): RecipeFields['category'] {\n    const category = this.recipe.recipeCategory\n\n    if (!category) {\n      throw new SchemaOrgException('category')\n    }\n\n    return this.schemaValueToList(category)\n  }\n\n  public yields(): RecipeFields['yields'] {\n    const yields = this.getSchemaTextValue(\n      this.recipe.recipeYield ?? this.recipe.yield,\n    )\n\n    if (!yields) {\n      throw new SchemaOrgException('yields', yields)\n    }\n\n    return parseYields(yields)\n  }\n\n  public totalTime(): RecipeFields['totalTime'] {\n    const totalTime = this.parseDurationField('totalTime')\n\n    if (totalTime) return totalTime\n\n    const prepTime = this.parseDurationField('prepTime') ?? 0\n    const cookTime = this.parseDurationField('cookTime') ?? 0\n\n    if (prepTime || cookTime) {\n      return prepTime + cookTime\n    }\n\n    throw new SchemaOrgException('totalTime')\n  }\n\n  public cookTime(): RecipeFields['cookTime'] {\n    return this.parseDurationField('cookTime')\n  }\n\n  public prepTime(): RecipeFields['prepTime'] {\n    return this.parseDurationField('prepTime')\n  }\n\n  public cuisine(): RecipeFields['cuisine'] {\n    const cuisine = this.recipe.recipeCuisine\n\n    if (!cuisine) {\n      throw new SchemaOrgException('cuisine')\n    }\n\n    return this.schemaValueToList(cuisine)\n  }\n\n  public cookingMethod(): RecipeFields['cookingMethod'] {\n    const cookingMethod = this.getSchemaTextValue(this.recipe.cookingMethod)\n\n    if (!cookingMethod) {\n      throw new SchemaOrgException('cookingMethod')\n    }\n\n    return cookingMethod\n  }\n\n  public ratings(): RecipeFields['ratings'] {\n    let ratings =\n      this.recipe.aggregateRating ??\n      this.findEntity(this.recipe, 'AggregateRating') // @TODO needed?\n\n    let ratingValue: string | undefined\n\n    if (isAggregateRating(ratings)) {\n      const ratingId = ratings['@id']\n\n      if (ratingId && this.ratingsData[ratingId]) {\n        ratings = this.ratingsData[ratingId]\n      }\n\n      ratingValue = this.getSchemaTextValue(ratings.ratingValue)\n    }\n\n    if (!ratingValue) {\n      throw new SchemaOrgException('ratings')\n    }\n\n    let value = Number.parseFloat(ratingValue)\n\n    if (isAggregateRating(ratings) && value > 5) {\n      const bestRating = Number.parseFloat(\n        this.getSchemaTextValue(ratings.bestRating),\n      )\n      const worstRating = Number.parseFloat(\n        this.getSchemaTextValue(ratings.worstRating),\n      )\n\n      if (!Number.isNaN(bestRating) && bestRating > 5) {\n        const lowerBound = Number.isNaN(worstRating) ? 0 : worstRating\n        const range = bestRating - lowerBound\n\n        if (range > 0) {\n          value = ((value - lowerBound) / range) * 5\n        }\n      }\n    }\n\n    return Math.round(value * 100) / 100\n  }\n\n  public ratingsCount(): RecipeFields['ratingsCount'] {\n    let ratings =\n      this.recipe.aggregateRating ??\n      this.findEntity(this.recipe, 'AggregateRating')\n\n    let ratingsCount: string | undefined\n\n    if (isAggregateRating(ratings)) {\n      const ratingId = ratings['@id']\n\n      if (ratingId && this.ratingsData[ratingId]) {\n        ratings = this.ratingsData[ratingId]\n      }\n\n      ratingsCount =\n        this.getSchemaTextValue(ratings.ratingCount) ||\n        this.getSchemaTextValue(ratings.reviewCount)\n    }\n\n    if (!ratingsCount) {\n      throw new SchemaOrgException('ratingsCount')\n    }\n\n    const count = Number.parseFloat(ratingsCount)\n    return count !== 0 ? Math.floor(count) : 0\n  }\n\n  public nutrients(): RecipeFields['nutrients'] {\n    const nutrients = this.recipe.nutrition\n\n    if (!isPlainObject(nutrients)) {\n      throw new SchemaOrgException('nutrients', nutrients)\n    }\n\n    const cleanedNutrients = new Map<string, string>()\n\n    for (const [key, value] of Object.entries(nutrients)) {\n      if (!key || key.startsWith('@') || !value) continue\n      cleanedNutrients.set(key, this.getSchemaTextValue(value))\n    }\n\n    return cleanedNutrients\n  }\n\n  public keywords(): RecipeFields['keywords'] {\n    const keywords = this.recipe.keywords\n\n    if (!keywords) {\n      throw new SchemaOrgException('keywords')\n    }\n\n    return this.schemaValueToList(keywords)\n  }\n\n  public dietaryRestrictions(): RecipeFields['dietaryRestrictions'] {\n    const dietaryRestrictions = this.recipe.suitableForDiet\n\n    if (!dietaryRestrictions) {\n      throw new SchemaOrgException('dietaryRestrictions')\n    }\n\n    const restrictionList = new Set<string>()\n    const list = this.schemaValueToList(dietaryRestrictions)\n\n    for (const item of list) {\n      const value = item.replace(/^https?:\\/\\/schema\\.org\\//, '')\n      if (value) {\n        restrictionList.add(value)\n      }\n    }\n\n    return restrictionList\n  }\n}\n","import type {\n  OptionalRecipeFields,\n  RecipeFields,\n} from './types/recipe.interface'\n\n// Default values for optional recipe fields\nconst OPTIONAL_RECIPE_FIELD_DEFAULT_VALUES = {\n  siteName: null,\n  category: new Set<string>(),\n  cookTime: null,\n  prepTime: null,\n  totalTime: null,\n  cuisine: new Set<string>(),\n  cookingMethod: null,\n  ratings: 0,\n  ratingsCount: 0,\n  equipment: new Set<string>(),\n  reviews: new Map<string, string>(),\n  nutrients: new Map<string, string>(),\n  dietaryRestrictions: new Set<string>(),\n  keywords: new Set<string>(),\n  notes: undefined,\n} as const satisfies OptionalRecipeFields\n\ntype OptionalRecipeFieldDefaultValues =\n  typeof OPTIONAL_RECIPE_FIELD_DEFAULT_VALUES\n\ntype OptionalRecipeFieldWithDefault = keyof OptionalRecipeFieldDefaultValues\n\nexport function isOptionalRecipeField(\n  field: keyof RecipeFields,\n): field is OptionalRecipeFieldWithDefault {\n  return field in OPTIONAL_RECIPE_FIELD_DEFAULT_VALUES\n}\n\nexport function getOptionalRecipeFieldDefault<\n  Key extends OptionalRecipeFieldWithDefault,\n>(field: Key): OptionalRecipeFieldDefaultValues[Key] {\n  const value = OPTIONAL_RECIPE_FIELD_DEFAULT_VALUES[field]\n\n  if (value instanceof Set) {\n    return new Set(value) as OptionalRecipeFieldDefaultValues[Key]\n  }\n\n  if (value instanceof Map) {\n    return new Map(value) as OptionalRecipeFieldDefaultValues[Key]\n  }\n\n  return value\n}\n","import type { ExtractorPlugin } from './abstract-extractor-plugin'\nimport {\n  getOptionalRecipeFieldDefault,\n  isOptionalRecipeField,\n} from './constants'\nimport {\n  ExtractionFailedException,\n  ExtractionRuntimeException,\n  ExtractorNotFoundException,\n} from './exceptions'\nimport { Logger, type LogLevel } from './logger'\nimport type { RecipeFields } from './types/recipe.interface'\nimport { isDefined } from './utils'\n\nexport class RecipeExtractor {\n  private readonly logger: Logger\n\n  constructor(\n    private plugins: ExtractorPlugin[],\n    private readonly scraperName: string,\n    private readonly options: { logLevel?: LogLevel } = {},\n  ) {\n    this.logger = new Logger(this.getContext(), this.options.logLevel)\n\n    // Sort plugins by priority in descending order (higher priority first)\n    this.plugins.sort((a, b) => b.priority - a.priority)\n  }\n\n  private getContext(context?: string) {\n    return `${this.scraperName}.${RecipeExtractor.name}${\n      context ? `.${context}` : ''\n    }`\n  }\n\n  async extract<Key extends keyof RecipeFields>(\n    field: Key,\n    extractor?: (\n      prevValue: RecipeFields[Key] | undefined,\n    ) => RecipeFields[Key] | Promise<RecipeFields[Key]>,\n  ): Promise<RecipeFields[Key]> {\n    let result: RecipeFields[Key] | undefined\n\n    this.logger.debug(`Extracting field: ${field}`)\n\n    // 1. Plugins in priority order\n    for (const plugin of this.plugins) {\n      const pluginLogger = new Logger(\n        this.getContext(plugin.name),\n        this.options.logLevel,\n      )\n      const isSupported = plugin.supports(field)\n\n      // Check if the plugin supports the field and if the result\n      // is not already defined--since plugins are sorted by priority,\n      // we only want to keep the value of the first plugin\n      // that returns a value for the field.\n      if (isSupported && !isDefined(result)) {\n        try {\n          result = await plugin.extract(field)\n        } catch (err) {\n          if (err instanceof ExtractionFailedException) {\n            pluginLogger.verbose(err.message)\n          } else {\n            throw new ExtractionRuntimeException(\n              field,\n              `plugin \"${plugin.name}\"`,\n              err,\n            )\n          }\n        }\n      } else {\n        pluginLogger.verbose(`Field is not supported: ${field}`)\n      }\n    }\n\n    // 2. Site-specific extractor\n    if (extractor) {\n      this.logger.debug(`Using site-specific extractor for: ${field}`)\n      this.logger.verbose('Current result: ', result)\n\n      try {\n        result = await extractor(result)\n        this.logger.verbose(`Site result for ${field}: `, result)\n      } catch (err) {\n        if (err instanceof ExtractionFailedException) {\n          this.logger.verbose(err.message)\n        } else {\n          throw new ExtractionRuntimeException(\n            field,\n            'site-specific extractor',\n            err,\n          )\n        }\n      }\n    }\n\n    // 3. Fallback to default values\n    if (!result && isOptionalRecipeField(field)) {\n      this.logger.debug(`Using default value for: ${field}`)\n      result = getOptionalRecipeFieldDefault(field) as RecipeFields[Key]\n    }\n\n    if (isDefined(result)) {\n      return result\n    }\n\n    throw new ExtractorNotFoundException(field)\n  }\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { getDotPath } from '@standard-schema/utils'\nimport {\n  isFunction,\n  isNumber,\n  isObjectLike,\n  isPlainObject,\n  isString,\n} from '@/utils'\n\n/**\n * A normalized validation issue used across supported schema libraries.\n */\nexport type ValidationIssue = StandardSchemaV1.Issue & {\n  dotPath?: string | null\n}\n\nexport type SafeParseErrorType = 'validation' | 'extraction'\n\nexport type SafeParseErrorCode =\n  | 'validation_failed'\n  | 'extractor_not_found'\n  | 'extraction_runtime_error'\n  | 'extraction_failed'\n\nexport interface SafeParseErrorContext {\n  field?: string\n  source?: string\n}\n\n/**\n * Validation error payload returned by `safeParse`.\n */\nexport interface SafeParseError {\n  type: SafeParseErrorType\n  code: SafeParseErrorCode\n  issues: ReadonlyArray<ValidationIssue>\n  cause?: unknown\n  context?: SafeParseErrorContext\n}\n\n/**\n * Library-agnostic safe parse result.\n */\nexport type SafeParseResult<T> =\n  | { success: true; data: T }\n  | { success: false; error: SafeParseError }\n\nconst isValidationPathSegment = (value: unknown): value is PropertyKey => {\n  return isString(value) || isNumber(value) || typeof value === 'symbol'\n}\n\nconst isStandardSchemaPathSegment = (\n  value: unknown,\n): value is StandardSchemaV1.PathSegment => {\n  return (\n    isPlainObject(value) && 'key' in value && isValidationPathSegment(value.key)\n  )\n}\n\nconst normalizeIssuePath = (\n  path?: ReadonlyArray<PropertyKey | StandardSchemaV1.PathSegment>,\n) => {\n  if (!path) return undefined\n\n  const normalizedPath: PropertyKey[] = []\n\n  for (const part of path) {\n    if (isValidationPathSegment(part)) {\n      normalizedPath.push(part)\n    } else if (isStandardSchemaPathSegment(part)) {\n      normalizedPath.push(part.key)\n    }\n  }\n\n  return normalizedPath.length > 0 ? normalizedPath : undefined\n}\n\nconst createFailureResult = (\n  issues: readonly StandardSchemaV1.Issue[],\n  cause?: unknown,\n  options?: {\n    type?: SafeParseErrorType\n    code?: SafeParseErrorCode\n    context?: SafeParseErrorContext\n  },\n): SafeParseResult<never> => {\n  return {\n    success: false,\n    error: {\n      type: options?.type ?? 'validation',\n      code: options?.code ?? 'validation_failed',\n      issues: issues.map((issue) => ({\n        message: issue.message,\n        path: normalizeIssuePath(issue.path),\n        dotPath: getDotPath(issue),\n      })),\n      cause,\n      context: options?.context,\n    },\n  }\n}\n\nconst isSuccessResult = <T>(\n  result: StandardSchemaV1.Result<T>,\n): result is StandardSchemaV1.SuccessResult<T> => {\n  return !result.issues\n}\n\n/**\n * Runtime check for Standard Schema compatibility.\n */\nexport function isStandardSchemaV1<Output>(\n  value: unknown,\n): value is StandardSchemaV1<unknown, Output> {\n  if (!isObjectLike(value) || !('~standard' in value)) {\n    return false\n  }\n\n  const standard = value['~standard']\n\n  return (\n    isPlainObject(standard) &&\n    standard.version === 1 &&\n    isString(standard.vendor) &&\n    isFunction(standard.validate)\n  )\n}\n\n/**\n * Validates input using any Standard Schema-compatible schema.\n */\nexport async function safeParseWithStandardSchema<T>(\n  schema: StandardSchemaV1<unknown, T>,\n  value: unknown,\n): Promise<SafeParseResult<T>> {\n  try {\n    const result = await schema['~standard'].validate(value)\n\n    if (isSuccessResult(result)) {\n      return { success: true, data: result.value }\n    }\n\n    return createFailureResult(result.issues)\n  } catch (error) {\n    const message =\n      error instanceof Error ? error.message : 'Schema validation failed'\n\n    return createFailureResult([{ message }], error)\n  }\n}\n","import type { NoteGroup, NoteItem, Notes } from '@/types/recipe.interface'\nimport { isPlainObject, isString } from './index'\n\n/**\n * Creates a NoteItem.\n */\nexport function createNoteItem(value: string): NoteItem {\n  return { value }\n}\n\n/**\n * Creates a NoteGroup.\n */\nexport function createNoteGroup(\n  name: string | null,\n  items: NoteItem[] = [],\n): NoteGroup {\n  return { name, items }\n}\n\n/**\n * Type guard to check if value is a NoteItem.\n */\nexport function isNoteItem(value: unknown): value is NoteItem {\n  return isPlainObject(value) && 'value' in value && isString(value.value)\n}\n\n/**\n * Type guard to check if value is a NoteGroup.\n */\nexport function isNoteGroup(value: unknown): value is NoteGroup {\n  return (\n    isPlainObject(value) &&\n    'name' in value &&\n    'items' in value &&\n    Array.isArray(value.items) &&\n    value.items.every(isNoteItem)\n  )\n}\n\n/**\n * Type guard to check if value is a Notes array.\n */\nexport function isNotes(value: unknown): value is Notes {\n  return Array.isArray(value) && value.every(isNoteGroup)\n}\n\n/**\n * Converts an array of strings to a Notes array with a single default group.\n */\nexport function stringsToNotes(\n  values: string[],\n  groupName: string | null = null,\n): Notes {\n  const items = values.map(createNoteItem)\n  return [createNoteGroup(groupName, items)]\n}\n","import type { CheerioAPI } from 'cheerio'\nimport { stringsToNotes } from './notes'\nimport { normalizeString } from './parsing'\n\nfunction extractNoteText($: CheerioAPI, selector: Parameters<CheerioAPI>[0]) {\n  if (!selector) {\n    return undefined\n  }\n\n  const text = normalizeString(\n    $(selector)\n      .text()\n      .replace(/\\u00A0/g, ' '),\n  )\n  return text || undefined\n}\n\n/**\n * Extracts recipe notes from WP Recipe Maker HTML.\n * Returns a single unnamed note group, or undefined when no note block exists.\n */\nexport function extractWprmNotes($: CheerioAPI) {\n  const recipeContainer = $('.wprm-recipe-container').first()\n\n  const nestedNotes = recipeContainer\n    .find('.wprm-recipe-notes-container')\n    .first()\n\n  const notesContainer =\n    nestedNotes.length > 0\n      ? nestedNotes\n      : $('.wprm-recipe-notes-container').first()\n\n  if (notesContainer.length === 0) {\n    return undefined\n  }\n\n  const notesRoot = notesContainer.find('.wprm-recipe-notes').first()\n\n  if (notesRoot.length === 0) {\n    return undefined\n  }\n\n  const listItemValues = notesRoot\n    .find('li')\n    .map((_, el) => extractNoteText($, el))\n    .get()\n    .filter(Boolean)\n\n  if (listItemValues.length > 0) {\n    return stringsToNotes(listItemValues)\n  }\n\n  const noteValues = notesRoot\n    .contents()\n    .toArray()\n    .flatMap((node) => {\n      if (node.type !== 'tag') {\n        return []\n      }\n\n      const child = $(node)\n\n      if (child.is('.wprm-spacer')) {\n        return []\n      }\n\n      const text = extractNoteText($, node)\n      return text ? [text] : []\n    })\n\n  return noteValues.length > 0 ? stringsToNotes(noteValues) : undefined\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport * as cheerio from 'cheerio'\nimport type { ParseIngredientOptions } from 'parse-ingredient'\nimport { RecipeObjectSchema } from '@/schemas/recipe.schema'\nimport type { ExtractorPlugin } from './abstract-extractor-plugin'\nimport type { PostProcessorPlugin } from './abstract-postprocessor-plugin'\nimport {\n  ExtractionFailedException,\n  ExtractionRuntimeException,\n  ExtractorNotFoundException,\n  NotImplementedException,\n  ValidationException,\n} from './exceptions'\nimport { Logger, LogLevel } from './logger'\nimport { PluginManager } from './plugin-manager'\nimport { HtmlStripperPlugin } from './plugins/html-stripper.processor'\nimport { IngredientParserPlugin } from './plugins/ingredient-parser.processor'\nimport { OpenGraphPlugin } from './plugins/opengraph.extractor'\nimport { SchemaOrgPlugin } from './plugins/schema-org.extractor'\nimport { RecipeExtractor } from './recipe-extractor'\nimport {\n  isStandardSchemaV1,\n  type SafeParseResult,\n  safeParseWithStandardSchema,\n} from './schema-adapter'\nimport type {\n  RecipeData,\n  RecipeFields,\n  RecipeObject,\n} from './types/recipe.interface'\nimport type { ScraperOptions } from './types/scraper.interface'\nimport { isPlainObject, resolveErrorMessage } from './utils'\nimport { extractWprmNotes } from './utils/extract-wprm-notes'\n\nexport type RecipeFieldExtractor<Key extends keyof RecipeFields> = (\n  prevValue: RecipeFields[Key] | undefined,\n) => RecipeFields[Key] | Promise<RecipeFields[Key]>\n\nexport type ScraperExtractors = {\n  [Key in keyof RecipeFields]?: RecipeFieldExtractor<Key>\n}\n\nexport abstract class AbstractScraper {\n  protected readonly logger: Logger\n  protected readonly pluginManager: PluginManager\n  protected readonly recipeExtractor: RecipeExtractor\n  private validationSchema: StandardSchemaV1<unknown, RecipeObject> | null =\n    null\n\n  public readonly $: cheerio.CheerioAPI\n  public recipeData: RecipeData | null = null\n\n  constructor(\n    protected readonly html: string,\n    protected readonly url: string,\n    protected readonly options: ScraperOptions = {},\n  ) {\n    const {\n      extraExtractors = [],\n      extraPostProcessors = [],\n      logLevel = LogLevel.WARN,\n      parseIngredients = false,\n    } = options\n\n    this.logger = new Logger(this.constructor.name, logLevel)\n    this.$ = cheerio.load(html)\n\n    const baseExtractors: ExtractorPlugin[] = [\n      new OpenGraphPlugin(this.$),\n      new SchemaOrgPlugin(this.$, logLevel),\n    ]\n\n    const basePostProcessors: PostProcessorPlugin[] = [new HtmlStripperPlugin()]\n\n    // Add ingredient parser if enabled\n    if (parseIngredients) {\n      const parserOptions: ParseIngredientOptions = isPlainObject(\n        parseIngredients,\n      )\n        ? parseIngredients\n        : {}\n      basePostProcessors.push(new IngredientParserPlugin(parserOptions))\n    }\n\n    this.pluginManager = new PluginManager(\n      baseExtractors,\n      basePostProcessors,\n      extraExtractors,\n      extraPostProcessors,\n    )\n\n    this.recipeExtractor = new RecipeExtractor(\n      this.pluginManager.getExtractors(),\n      this.constructor.name,\n      { logLevel },\n    )\n  }\n\n  /**\n   * Site-specific extractors (implemented by subclasses)\n   * Each extractor is a function that takes the previous value\n   * returned by the extractor chain (if any) and returns the field value.\n   */\n  protected readonly extractors: ScraperExtractors = {}\n\n  /**\n   * Main extraction method - tries site-specific first, then plugins,\n   * then applies post-processing.\n   */\n  public async extract<Key extends keyof RecipeFields>(\n    field: Key,\n  ): Promise<RecipeFields[Key]> {\n    // 1. Extract the raw value\n    let value = await this.recipeExtractor.extract(\n      field,\n      this.extractors[field],\n    )\n\n    // 2. Apply post-processors\n    for (const processor of this.pluginManager.getPostProcessors()) {\n      value = await processor.process(field, value)\n    }\n\n    return value\n  }\n\n  /**\n   * Static method to get the host of the scraper.\n   * This should be implemented by subclasses to return the specific host.\n   */\n  static host(): string {\n    throw new NotImplementedException('host')\n  }\n\n  /**\n   * Returns the host value stored in the final recipe data.\n   * Subclasses can override when host must be derived from instance context.\n   */\n  protected getHost(): string {\n    const instance = this.constructor as typeof AbstractScraper\n    return instance.host()\n  }\n\n  /*****************************************************************************\n   * Default implementations for common fields that can be overridden\n   * by subclasses.\n   ****************************************************************************/\n\n  canonicalUrl(): RecipeFields['canonicalUrl'] {\n    const canonicalLink = this.$('link[rel=\"canonical\"]').attr('href')\n\n    const base = new URL(\n      this.url.startsWith('http') ? this.url : `https://${this.url}`,\n    )\n\n    return canonicalLink ? new URL(canonicalLink, base).href : base.href\n  }\n\n  language(): RecipeFields['language'] {\n    const langAttr = this.$('html').attr('lang')\n\n    if (langAttr) {\n      return langAttr\n    }\n\n    // Deprecated: check for a meta http-equiv header\n    // See: https://www.w3.org/International/questions/qa-http-and-lang\n    const metaLang = this.$('meta[http-equiv=\"content-language\"]').attr(\n      'content',\n    )\n\n    if (metaLang) {\n      return metaLang.split(',')[0]\n    }\n\n    this.logger.warn('Could not determine language')\n\n    return 'en' // Default to English if not found\n  }\n\n  links(): RecipeFields['links'] {\n    if (!this.options.linksEnabled) return undefined\n\n    return this.$('a[href]')\n      .map((_, el) => {\n        const href = this.$(el).attr('href')\n        if (!href?.startsWith('http')) return null\n        return { href, text: this.$(el).text().trim() }\n      })\n      .get()\n      .filter(Boolean)\n  }\n\n  protected notes(): RecipeData['notes'] {\n    return extractWprmNotes(this.$)\n  }\n\n  private async extractYields(): Promise<RecipeFields['yields']> {\n    try {\n      return await this.extract('yields')\n    } catch (error) {\n      const fallbackYield = this.options.fallbackYield?.trim()\n\n      if (error instanceof ExtractorNotFoundException && fallbackYield) {\n        return fallbackYield\n      }\n\n      throw error\n    }\n  }\n\n  /**\n   * Scrape's the recipe and caches the data.\n   */\n  public async scrape(): Promise<RecipeData> {\n    if (this.recipeData) {\n      return this.recipeData\n    }\n\n    const notes = this.options.parseNotes ? this.notes() : undefined\n\n    this.recipeData = {\n      author: await this.extract('author'),\n      canonicalUrl: this.canonicalUrl(),\n      category: await this.extract('category'),\n      cookTime: await this.extract('cookTime'),\n      cookingMethod: await this.extract('cookingMethod'),\n      cuisine: await this.extract('cuisine'),\n      description: await this.extract('description'),\n      dietaryRestrictions: await this.extract('dietaryRestrictions'),\n      equipment: await this.extract('equipment'),\n      host: this.getHost(),\n      image: await this.extract('image'),\n      ingredients: await this.extract('ingredients'),\n      instructions: await this.extract('instructions'),\n      ...(notes ? { notes } : {}),\n      keywords: await this.extract('keywords'),\n      language: this.language(),\n      links: this.links(),\n      nutrients: await this.extract('nutrients'),\n      prepTime: await this.extract('prepTime'),\n      ratings: await this.extract('ratings'),\n      ratingsCount: await this.extract('ratingsCount'),\n      reviews: await this.extract('reviews'),\n      siteName: await this.extract('siteName'),\n      title: await this.extract('title'),\n      totalTime: await this.extract('totalTime'),\n      yields: await this.extractYields(),\n    }\n\n    return this.recipeData\n  }\n\n  /**\n   * Converts the scraper's data into a JSON-serializable object.\n   * Note: schemaVersion is added during validation by parse() or safeParse().\n   */\n  public async toRecipeObject(): Promise<Omit<RecipeObject, 'schemaVersion'>> {\n    const {\n      category,\n      cuisine,\n      dietaryRestrictions,\n      equipment,\n      keywords,\n      nutrients,\n      reviews,\n      ...rest\n    } = await this.scrape()\n\n    return {\n      ...rest,\n      category: Array.from(category),\n      cuisine: Array.from(cuisine),\n      dietaryRestrictions: Array.from(dietaryRestrictions),\n      equipment: Array.from(equipment),\n      keywords: Array.from(keywords),\n      nutrients: Object.fromEntries(nutrients),\n      reviews: Object.fromEntries(reviews),\n    }\n  }\n\n  /**\n   * Get the default schema used for validation.\n   * Subclasses can override this method to customize the default schema.\n   * For custom validation schemas, pass `schema` in options.\n   */\n  protected getSchema() {\n    return RecipeObjectSchema\n  }\n\n  /**\n   * Resolve the Standard Schema used for validation.\n   *\n   * Resolution order:\n   * 1) `options.schema`\n   * 2) default schema from `getSchema()`\n   */\n  protected getValidationSchema(): StandardSchemaV1<unknown, RecipeObject> {\n    if (this.validationSchema) {\n      return this.validationSchema\n    }\n\n    const schema = this.options.schema ?? this.getSchema()\n\n    if (!isStandardSchemaV1<RecipeObject>(schema)) {\n      throw new Error(\n        'Validation schema must be Standard Schema v1 compatible.',\n      )\n    }\n\n    this.validationSchema = schema\n    return this.validationSchema\n  }\n\n  /**\n   * Extract and validate recipe data.\n   * Throws ValidationException if validation fails.\n   *\n   * @returns Validated recipe object\n   * @throws {ValidationException} If validation fails\n   */\n  async parse(): Promise<RecipeObject> {\n    const raw = await this.toRecipeObject()\n    const result = await safeParseWithStandardSchema(\n      this.getValidationSchema(),\n      raw,\n    )\n\n    if (!result.success) {\n      throw new ValidationException(result.error.issues, result.error.cause)\n    }\n\n    return result.data\n  }\n\n  /**\n   * Extract and validate recipe data without throwing.\n   * Returns a result object indicating success or failure.\n   *\n   * @returns Result object with either data or error\n   */\n  async safeParse(): Promise<SafeParseResult<RecipeObject>> {\n    try {\n      const raw = await this.toRecipeObject()\n      return safeParseWithStandardSchema(this.getValidationSchema(), raw)\n    } catch (error) {\n      if (error instanceof ExtractorNotFoundException) {\n        return {\n          success: false,\n          error: {\n            type: 'extraction',\n            code: 'extractor_not_found',\n            context: { field: error.field },\n            issues: [\n              {\n                message: error.message,\n                path: [error.field],\n                dotPath: error.field,\n              },\n            ],\n            cause: error,\n          },\n        }\n      }\n\n      if (error instanceof ExtractionRuntimeException) {\n        return {\n          success: false,\n          error: {\n            type: 'extraction',\n            code: 'extraction_runtime_error',\n            context: { field: error.field, source: error.source },\n            issues: [\n              {\n                message: error.message,\n                path: [error.field],\n                dotPath: error.field,\n              },\n            ],\n            cause: error.extractionCause ?? error,\n          },\n        }\n      }\n\n      if (error instanceof ExtractionFailedException) {\n        return {\n          success: false,\n          error: {\n            type: 'extraction',\n            code: 'extraction_failed',\n            context: { field: error.field },\n            issues: [\n              {\n                message: error.message,\n                path: [error.field],\n                dotPath: error.field,\n              },\n            ],\n            cause: error,\n          },\n        }\n      }\n\n      const message = resolveErrorMessage(error, 'Recipe extraction failed')\n\n      return {\n        success: false,\n        error: {\n          type: 'extraction',\n          code: 'extraction_failed',\n          issues: [{ message }],\n          cause: error,\n        },\n      }\n    }\n  }\n}\n","import z from 'zod'\nimport { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { Ingredients, RecipeFields } from '@/types/recipe.interface'\nimport {\n  createIngredientGroup,\n  createIngredientItem,\n  flattenIngredients,\n  groupIngredients,\n} from '@/utils/ingredients'\nimport {\n  createInstructionGroup,\n  createInstructionItem,\n} from '@/utils/instructions'\nimport { normalizeString } from '@/utils/parsing'\n\nconst recipeIngredientItemSchema = z.object({\n  fields: z.object({\n    qty: z.string(),\n    preText: z.string(),\n    postText: z.string(),\n    measurement: z.string().nullable(),\n    pluralIngredient: z.boolean(),\n    ingredient: z.object({\n      contentType: z.string(),\n      fields: z.object({\n        title: z.string(),\n        pluralTitle: z.string(),\n        kind: z.string(),\n      }),\n    }),\n  }),\n})\n\ntype RecipeIngredientItem = z.infer<typeof recipeIngredientItemSchema>\n\nconst recipeIngredientGroupSchema = z.object({\n  fields: z.object({\n    title: z.string(),\n    recipeIngredientItems: z.array(recipeIngredientItemSchema),\n  }),\n})\n\nconst recipeInstructionSchema = z.object({\n  fields: z.object({\n    content: z.string(),\n  }),\n})\n\nconst recipeDataSchema = z.object({\n  totalCookTime: z.number(),\n  recipeTimeNote: z.string().optional(),\n  ingredientGroups: z.array(recipeIngredientGroupSchema),\n  headnote: z.string().optional(),\n  instructions: z.array(recipeInstructionSchema),\n  metaData: z.object({\n    fields: z.object({\n      photo: z.object({\n        url: z.url(),\n      }),\n    }),\n  }),\n})\n\ntype RecipeData = z.infer<typeof recipeDataSchema>\n\nconst pagePropsDataSchema = z.object({\n  props: z.object({\n    pageProps: z.object({\n      data: recipeDataSchema,\n    }),\n  }),\n})\n\nexport class AmericasTestKitchen extends AbstractScraper {\n  private data: RecipeData | null = null\n\n  static host() {\n    return 'americastestkitchen.com'\n  }\n\n  protected override readonly extractors = {\n    image: this.image.bind(this),\n    ingredients: this.ingredients.bind(this),\n    instructions: this.instructions.bind(this),\n    siteName: this.siteName.bind(this),\n  } satisfies ScraperExtractors\n\n  protected siteName(): RecipeFields['siteName'] {\n    return \"America's Test Kitchen\"\n  }\n\n  protected image(\n    prevValue: RecipeFields['image'] | undefined,\n  ): RecipeFields['image'] {\n    const data = this.getRecipeData()\n\n    if (!data) {\n      if (prevValue) {\n        return prevValue\n      }\n      throw new Error('Failed to extract image')\n    }\n\n    return data.metaData.fields.photo.url\n  }\n\n  protected ingredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] {\n    // First try to parse structured data\n    // If that fails, try to parse HTML ingredients\n    let ingredients = this.parseIngredients()\n\n    if (!ingredients) {\n      ingredients = this.parseHtmlIngredients(prevValue)\n    }\n\n    if (!ingredients) {\n      throw new Error('Failed to extract ingredients')\n    }\n\n    return ingredients\n  }\n\n  protected instructions(\n    prevValue: RecipeFields['instructions'] | undefined,\n  ): RecipeFields['instructions'] {\n    const data = this.getRecipeData()\n\n    if (!data) {\n      if (prevValue) {\n        return prevValue\n      }\n      throw new Error('Failed to extract instructions')\n    }\n\n    const { headnote } = data\n\n    const items: string[] = []\n\n    if (headnote) {\n      items.push(`Note: ${normalizeString(headnote)}`)\n    }\n\n    for (const instruction of data.instructions) {\n      items.push(normalizeString(instruction.fields.content))\n    }\n\n    return [createInstructionGroup(null, items.map(createInstructionItem))]\n  }\n\n  private parseHtmlIngredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] | null {\n    // Use wildcard selectors to handle dynamic class name suffixes\n    const headingSelector = '[class*=\"RecipeIngredientGroups_group\"] > span'\n    const ingredientSelector = '[class*=\"RecipeIngredient\"] label'\n\n    if (prevValue && prevValue.length > 0) {\n      const values = flattenIngredients(prevValue)\n      return groupIngredients(\n        this.$,\n        values,\n        headingSelector,\n        ingredientSelector,\n      )\n    }\n\n    return null\n  }\n\n  private getRecipeData(): RecipeData | null {\n    if (this.data === null) {\n      const jsonElement = this.$('script[type=\"application/json\"]')\n      const jsonString = jsonElement.html()\n\n      if (!jsonString) {\n        this.logger.warn('Could not find JSON data script tag')\n        return null\n      }\n\n      try {\n        const parsed = pagePropsDataSchema.parse(JSON.parse(jsonString))\n        this.data = parsed.props.pageProps.data\n      } catch (error) {\n        this.logger.error('Failed to parse JSON data:', error)\n        return null\n      }\n    }\n\n    return this.data\n  }\n\n  private parseIngredientItem(ingredientItem: RecipeIngredientItem): string {\n    const { fields } = ingredientItem\n    const fragments = [\n      fields.qty || '',\n      fields.measurement || '',\n      fields.ingredient.fields.title || '',\n      fields.postText || '',\n    ]\n\n    const filteredFragments: string[] = []\n\n    for (const fragment of fragments) {\n      if (fragment) {\n        filteredFragments.push(fragment.trimEnd())\n      }\n    }\n\n    return filteredFragments.join(' ').trimEnd().replace(' ,', ',')\n  }\n\n  private parseIngredients(): RecipeFields['ingredients'] | null {\n    const data = this.getRecipeData()\n\n    if (!data) {\n      return null\n    }\n\n    const { ingredientGroups } = data\n\n    const result: Ingredients = []\n\n    for (const group of ingredientGroups) {\n      const groupTitle =\n        group.fields.title.length === 0 ? null : group.fields.title\n      const items = group.fields.recipeIngredientItems.map((item) =>\n        createIngredientItem(this.parseIngredientItem(item)),\n      )\n\n      result.push(createIngredientGroup(groupTitle, items))\n    }\n\n    return result\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport { NoIngredientsFoundException } from '@/exceptions'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport { flattenIngredients, groupIngredients } from '@/utils/ingredients'\n\nexport class BBCGoodFood extends AbstractScraper {\n  static host() {\n    return 'bbcgoodfood.com'\n  }\n\n  protected override readonly extractors = {\n    ingredients: this.ingredients.bind(this),\n  } satisfies ScraperExtractors\n\n  protected ingredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] {\n    const headingSelector = '.recipe__ingredients h3'\n    const ingredientSelector = '.recipe__ingredients li'\n\n    if (prevValue && prevValue.length > 0) {\n      const values = flattenIngredients(prevValue)\n\n      return groupIngredients(\n        this.$,\n        values,\n        headingSelector,\n        ingredientSelector,\n      )\n    }\n\n    throw new NoIngredientsFoundException()\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport {\n  createIngredientGroup,\n  createIngredientItem,\n} from '@/utils/ingredients'\nimport {\n  createInstructionGroup,\n  createInstructionItem,\n} from '@/utils/instructions'\nimport { normalizeString } from '@/utils/parsing'\n\nexport class BongEats extends AbstractScraper {\n  static host() {\n    return 'bongeats.com'\n  }\n\n  protected override readonly extractors = {\n    ingredients: this.ingredients.bind(this),\n    instructions: this.instructions.bind(this),\n  } satisfies ScraperExtractors\n\n  protected ingredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] {\n    if (prevValue && prevValue.length > 0) {\n      return prevValue\n    }\n\n    const items = this.$('.recipe-ingredients li')\n      .toArray()\n      .map((element) => normalizeString(this.$(element).text()))\n      .filter((value) => value.length > 0)\n      .map(createIngredientItem)\n\n    if (items.length === 0) {\n      throw new Error('Failed to extract ingredients')\n    }\n\n    return [createIngredientGroup(null, items)]\n  }\n\n  protected instructions(\n    prevValue: RecipeFields['instructions'] | undefined,\n  ): RecipeFields['instructions'] {\n    if (prevValue && prevValue.length > 0) {\n      return prevValue\n    }\n\n    const items = this.$('.recipe-process li')\n      .toArray()\n      .map((element) => normalizeString(this.$(element).text()))\n      .filter((value) => value.length > 0)\n      .map(createInstructionItem)\n\n    if (items.length === 0) {\n      throw new Error('Failed to extract instructions')\n    }\n\n    return [createInstructionGroup(null, items)]\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport { NoIngredientsFoundException } from '@/exceptions'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport {\n  createIngredientGroup,\n  createIngredientItem,\n} from '@/utils/ingredients'\nimport {\n  createInstructionGroup,\n  createInstructionItem,\n  splitNumberedInstructions,\n} from '@/utils/instructions'\nimport { normalizeString, stripLeadingBullet } from '@/utils/parsing'\n\nfunction extractMetaContent($: AbstractScraper['$'], selector: string): string {\n  return normalizeString($(selector).attr('content'))\n}\n\nfunction stripSiteSuffix(value: string): string {\n  return normalizeString(value.replace(/\\s+[\\u2014-]\\s+Brian Lagerstrom$/, ''))\n}\n\nexport class BrianLagerstrom extends AbstractScraper {\n  static host() {\n    return 'brianlagerstrom.com'\n  }\n\n  protected override readonly extractors = {\n    author: this.author.bind(this),\n    description: this.description.bind(this),\n    ingredients: this.ingredients.bind(this),\n    instructions: this.instructions.bind(this),\n    title: this.title.bind(this),\n    yields: this.yields.bind(this),\n  } satisfies ScraperExtractors\n\n  protected title(\n    prevValue: RecipeFields['title'] | undefined,\n  ): RecipeFields['title'] {\n    const heading = normalizeString(\n      this.$('.entry-title[itemprop=\"headline\"]').text(),\n    )\n    if (heading) return heading\n\n    const metaTitle = extractMetaContent(this.$, 'meta[property=\"og:title\"]')\n    if (metaTitle) return stripSiteSuffix(metaTitle)\n\n    if (prevValue) return stripSiteSuffix(prevValue)\n\n    throw new Error('Failed to extract title')\n  }\n\n  protected author(\n    prevValue: RecipeFields['author'] | undefined,\n  ): RecipeFields['author'] {\n    if (prevValue) return prevValue\n\n    const siteName = extractMetaContent(this.$, 'meta[property=\"og:site_name\"]')\n    if (siteName) return siteName\n\n    return 'Brian Lagerstrom'\n  }\n\n  protected description(\n    prevValue: RecipeFields['description'] | undefined,\n  ): RecipeFields['description'] {\n    const contentBlocks = this.recipeContentBlocks()\n    const ingredientBlockIndex = this.ingredientBlockIndex()\n    const introBlocks =\n      ingredientBlockIndex >= 0\n        ? contentBlocks.slice(0, ingredientBlockIndex)\n        : contentBlocks\n    const introParagraph = this.$(introBlocks)\n      .find('p')\n      .toArray()\n      .map((element) => normalizeString(this.$(element).text()))\n      .find(\n        (value) =>\n          value &&\n          !/^as an amazon/i.test(value) &&\n          !/^ingredients:?$/i.test(value) &&\n          !/^instructions:?$/i.test(value) &&\n          stripLeadingBullet(value) === value,\n      )\n\n    if (introParagraph) return introParagraph\n\n    const metaDescription =\n      extractMetaContent(this.$, 'meta[itemprop=\"description\"]') ||\n      extractMetaContent(this.$, 'meta[property=\"og:description\"]')\n    if (metaDescription) return metaDescription\n\n    if (prevValue) return prevValue\n\n    throw new Error('Failed to extract description')\n  }\n\n  private recipeContentBlocks() {\n    return this.$('.blog-item-content .sqs-html-content').toArray()\n  }\n\n  private ingredientBlockIndex(): number {\n    return this.recipeContentBlocks().findIndex((element) => {\n      const text = normalizeString(this.$(element).text())\n      return /ingredients:?/i.test(text)\n    })\n  }\n\n  protected ingredients(): RecipeFields['ingredients'] {\n    const contentBlocks = this.recipeContentBlocks()\n    const ingredientBlockIndex = this.ingredientBlockIndex()\n\n    if (ingredientBlockIndex < 0) {\n      throw new NoIngredientsFoundException()\n    }\n\n    const items: ReturnType<typeof createIngredientItem>[] = []\n\n    for (const element of contentBlocks.slice(ingredientBlockIndex)) {\n      const listItems = this.$(element).find('li').toArray()\n\n      if (listItems.length > 0) {\n        items.push(\n          ...listItems\n            .map((item) => stripLeadingBullet(this.$(item).text()))\n            .filter((value) => value.length > 0)\n            .map(createIngredientItem),\n        )\n        break\n      }\n\n      const paragraphs = this.$(element).find('p').toArray()\n\n      for (const paragraph of paragraphs) {\n        const text = normalizeString(this.$(paragraph).text())\n\n        if (/^instructions:?$/i.test(text)) {\n          return [createIngredientGroup(null, items)]\n        }\n\n        if (\n          !text ||\n          /^as an amazon/i.test(text) ||\n          /^ingredients:?$/i.test(text)\n        ) {\n          continue\n        }\n\n        const ingredient = stripLeadingBullet(text)\n\n        if (ingredient !== text) {\n          items.push(createIngredientItem(ingredient))\n        }\n      }\n    }\n\n    if (items.length === 0) {\n      throw new NoIngredientsFoundException()\n    }\n\n    return [createIngredientGroup(null, items)]\n  }\n\n  protected instructions(): RecipeFields['instructions'] {\n    const contentBlocks = this.recipeContentBlocks()\n    const ingredientBlockIndex = this.ingredientBlockIndex()\n\n    if (ingredientBlockIndex < 0) {\n      return []\n    }\n\n    for (const element of contentBlocks.slice(ingredientBlockIndex + 1)) {\n      const orderedListItems = this.$(element).find('ol li').toArray()\n\n      if (orderedListItems.length > 1) {\n        return [\n          createInstructionGroup(\n            null,\n            orderedListItems\n              .map((item) => normalizeString(this.$(item).text()))\n              .filter((value) => value.length > 0)\n              .map(createInstructionItem),\n          ),\n        ]\n      }\n    }\n\n    const steps: string[] = []\n\n    for (const element of contentBlocks.slice(ingredientBlockIndex + 1)) {\n      const text = normalizeString(this.$(element).text())\n\n      if (!text || /^faq/i.test(text) || /amazon affiliate/i.test(text)) {\n        break\n      }\n\n      steps.push(...splitNumberedInstructions(text))\n    }\n\n    return [\n      createInstructionGroup(\n        null,\n        steps.filter((value) => value.length > 0).map(createInstructionItem),\n      ),\n    ]\n  }\n\n  protected yields(\n    prevValue: RecipeFields['yields'] | undefined,\n  ): RecipeFields['yields'] {\n    if (prevValue) return prevValue\n\n    const bodyText = normalizeString(this.$('.blog-item-content').text())\n    const match = bodyText.match(\n      /\\b(?:serves?|yield(?:s)?)\\s*:?\\s*([0-9][^|.,;]*)/i,\n    )\n    const servings = normalizeString(match?.[1])\n\n    if (servings) {\n      return servings\n    }\n\n    return '1 recipe'\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { RecipeFields } from '@/types/recipe.interface'\n\nexport class DamnDelicious extends AbstractScraper {\n  static host() {\n    return 'damndelicious.net'\n  }\n\n  protected override readonly extractors = {\n    siteName: this.siteName.bind(this),\n  } satisfies ScraperExtractors\n\n  protected siteName(\n    _prevValue: RecipeFields['siteName'] | undefined,\n  ): RecipeFields['siteName'] {\n    return 'Damn Delicious'\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { RecipeFields } from '@/types/recipe.interface'\n\nexport class Epicurious extends AbstractScraper {\n  static host() {\n    return 'epicurious.com'\n  }\n\n  protected override readonly extractors = {\n    author: this.author.bind(this),\n  } satisfies ScraperExtractors\n\n  protected author(): RecipeFields['author'] {\n    const author = this.$('a[itemprop=\"author\"]').text().trim()\n    return author\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport { NoIngredientsFoundException } from '@/exceptions'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport { flattenIngredients, groupIngredients } from '@/utils/ingredients'\n\nexport class InspiredTaste extends AbstractScraper {\n  static host() {\n    return 'inspiredtaste.net'\n  }\n\n  protected override readonly extractors = {\n    ingredients: this.ingredients.bind(this),\n    siteName: this.siteName.bind(this),\n  } satisfies ScraperExtractors\n\n  protected ingredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] {\n    const headingSelector = '.ingredient_heading'\n    const ingredientSelector = '.itr-ingredients p'\n\n    if (prevValue && prevValue.length > 0) {\n      const values = flattenIngredients(prevValue)\n\n      return groupIngredients(\n        this.$,\n        values,\n        headingSelector,\n        ingredientSelector,\n      )\n    }\n\n    throw new NoIngredientsFoundException()\n  }\n\n  protected siteName(\n    _prevValue: RecipeFields['siteName'] | undefined,\n  ): RecipeFields['siteName'] {\n    return 'Inspired Taste'\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport {\n  createIngredientGroup,\n  createIngredientItem,\n} from '@/utils/ingredients'\nimport {\n  createInstructionGroup,\n  createInstructionItem,\n} from '@/utils/instructions'\nimport { normalizeString, parseMinutes } from '@/utils/parsing'\n\nexport class MyPlate extends AbstractScraper {\n  static host() {\n    return 'myplate.gov'\n  }\n\n  protected override readonly extractors = {\n    cookTime: this.cookTime.bind(this),\n    ingredients: this.ingredients.bind(this),\n    instructions: this.instructions.bind(this),\n    prepTime: this.prepTime.bind(this),\n    totalTime: this.totalTime.bind(this),\n  } satisfies ScraperExtractors\n\n  protected ingredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] {\n    if (prevValue && prevValue.length > 0) {\n      return prevValue\n    }\n\n    const items = this.$('.field--name-field-ingredients li.field__item')\n      .toArray()\n      .map((element) => normalizeString(this.$(element).text()))\n      .filter((value) => value.length > 0)\n      .map(createIngredientItem)\n\n    if (items.length === 0) {\n      throw new Error('Failed to extract ingredients')\n    }\n\n    return [createIngredientGroup(null, items)]\n  }\n\n  protected instructions(\n    prevValue: RecipeFields['instructions'] | undefined,\n  ): RecipeFields['instructions'] {\n    if (prevValue && prevValue.length > 0) {\n      return prevValue\n    }\n\n    const items = this.$('.field--name-field-instructions li')\n      .toArray()\n      .map((element) => normalizeString(this.$(element).text()))\n      .filter((value) => value.length > 0)\n      .map(createInstructionItem)\n\n    if (items.length === 0) {\n      throw new Error('Failed to extract instructions')\n    }\n\n    return [createInstructionGroup(null, items)]\n  }\n\n  protected cookTime(\n    prevValue: RecipeFields['cookTime'] | undefined,\n  ): RecipeFields['cookTime'] {\n    return this.readDuration(\n      '.mp-recipe-full__detail--cook-time .mp-recipe-full__detail--data',\n      prevValue,\n    )\n  }\n\n  protected prepTime(\n    prevValue: RecipeFields['prepTime'] | undefined,\n  ): RecipeFields['prepTime'] {\n    return this.readDuration(\n      '.mp-recipe-full__detail--prep-time .mp-recipe-full__detail--data',\n      prevValue,\n    )\n  }\n\n  protected totalTime(\n    prevValue: RecipeFields['totalTime'] | undefined,\n  ): RecipeFields['totalTime'] {\n    const cookTime = this.readDuration(\n      '.mp-recipe-full__detail--cook-time .mp-recipe-full__detail--data',\n      null,\n    )\n    const prepTime = this.readDuration(\n      '.mp-recipe-full__detail--prep-time .mp-recipe-full__detail--data',\n      null,\n    )\n\n    if (cookTime !== null || prepTime !== null) {\n      return (cookTime ?? 0) + (prepTime ?? 0)\n    }\n\n    return prevValue ?? null\n  }\n\n  private readDuration(\n    selector: string,\n    fallback: number | null | undefined,\n  ): number | null {\n    const value = normalizeString(this.$(selector).first().text())\n\n    if (!value) {\n      return fallback ?? null\n    }\n\n    try {\n      return parseMinutes(value)\n    } catch {\n      return fallback ?? null\n    }\n  }\n}\n","import z from 'zod'\nimport { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport { NoIngredientsFoundException } from '@/exceptions'\nimport type { RecipeData, RecipeFields } from '@/types/recipe.interface'\nimport { flattenIngredients, groupIngredients } from '@/utils/ingredients'\nimport { parseJsonWithRepair } from '@/utils/json'\nimport { stringsToNotes } from '@/utils/notes'\nimport { normalizeString } from '@/utils/parsing'\n\nconst nextDataSchema = z.object({\n  props: z.object({\n    pageProps: z.object({\n      recipe: z.object({\n        tips: z.array(z.string()).optional(),\n      }),\n    }),\n  }),\n})\n\ntype RecipePageData = z.infer<\n  typeof nextDataSchema\n>['props']['pageProps']['recipe']\n\nexport class NYTimes extends AbstractScraper {\n  private recipePageData: RecipePageData | null | undefined = undefined\n\n  static host() {\n    return 'cooking.nytimes.com'\n  }\n\n  protected override readonly extractors = {\n    ingredients: this.ingredients.bind(this),\n  } satisfies ScraperExtractors\n\n  protected ingredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] {\n    // Use wildcard selectors to handle dynamic class name suffixes\n    const headingSelector = 'h3[class*=\"ingredientgroup_name\"]'\n    const ingredientSelector = 'li[class*=\"ingredient\"]'\n\n    if (prevValue && prevValue.length > 0) {\n      const values = flattenIngredients(prevValue)\n\n      return groupIngredients(\n        this.$,\n        values,\n        headingSelector,\n        ingredientSelector,\n      )\n    }\n\n    throw new NoIngredientsFoundException()\n  }\n\n  protected override notes(): RecipeData['notes'] {\n    const payloadNotes = this.payloadNotes()\n\n    if (payloadNotes) {\n      return payloadNotes\n    }\n\n    return this.domNotes()\n  }\n\n  private getRecipePageData(): RecipePageData | null {\n    if (this.recipePageData !== undefined) {\n      return this.recipePageData\n    }\n\n    const raw = this.$('#__NEXT_DATA__').html()\n\n    if (!raw) {\n      this.logger.warn('Could not find NYTimes __NEXT_DATA__ payload')\n      this.recipePageData = null\n      return this.recipePageData\n    }\n\n    try {\n      const { data } = parseJsonWithRepair(raw)\n      const parsed = nextDataSchema.parse(data)\n      this.recipePageData = parsed.props.pageProps.recipe\n    } catch (error) {\n      this.logger.warn('Failed to parse NYTimes recipe payload', error)\n      this.recipePageData = null\n    }\n\n    return this.recipePageData\n  }\n\n  private payloadNotes(): RecipeData['notes'] {\n    const tips = this.getRecipePageData()?.tips ?? []\n    const values = tips.map(normalizeString).filter((value) => value.length > 0)\n\n    if (values.length === 0) {\n      return undefined\n    }\n\n    return stringsToNotes(values)\n  }\n\n  private domNotes(): RecipeData['notes'] {\n    const container = this.$('div[class*=\"tips_tips\"]').first()\n\n    if (container.length === 0) {\n      return undefined\n    }\n\n    const listValues = container\n      .find('li, p')\n      .toArray()\n      .map((element) => normalizeString(this.$(element).text()))\n      .filter((value) => value.length > 0)\n\n    if (listValues.length > 0) {\n      return stringsToNotes(listValues)\n    }\n\n    const content = container.clone()\n    content.find('.pantry--label').remove()\n\n    const text = normalizeString(content.text())\n\n    if (!text) {\n      return undefined\n    }\n\n    return stringsToNotes([text])\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport { normalizeString } from '@/utils/parsing'\n\nexport class OnceUponAChef extends AbstractScraper {\n  static host() {\n    return 'onceuponachef.com'\n  }\n\n  protected override readonly extractors = {\n    author: this.author.bind(this),\n  } satisfies ScraperExtractors\n\n  protected author(\n    prevValue: RecipeFields['author'] | undefined,\n  ): RecipeFields['author'] {\n    if (prevValue && normalizeString(prevValue)) {\n      return prevValue\n    }\n\n    const author = normalizeString(\n      this.$('meta[name=\"author\"]').attr('content') ??\n        this.$('meta[name=\"twitter:data1\"]').attr('content'),\n    )\n\n    if (!author) {\n      throw new Error('Failed to extract author')\n    }\n\n    return author\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport { NoIngredientsFoundException } from '@/exceptions'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport { flattenIngredients, groupIngredients } from '@/utils/ingredients'\nimport {\n  createInstructionGroup,\n  createInstructionItem,\n} from '@/utils/instructions'\nimport { normalizeString } from '@/utils/parsing'\n\n/**\n * Filters out ingredient group headers from a list of ingredient values.\n * SimplyRecipes JSON-LD includes group headers (like \"For the roasted\n * parsnips:\") as regular ingredients, so we need to remove them.\n */\nfunction filterGroupHeaders(values: string[]): string[] {\n  // Group headers typically start with \"For \" and end with \":\"\n  return values.filter((value) => {\n    const trimmed = value.trim().toLowerCase()\n    return !(trimmed.startsWith('for ') && trimmed.endsWith(':'))\n  })\n}\n\nexport class SimplyRecipes extends AbstractScraper {\n  static host() {\n    return 'simplyrecipes.com'\n  }\n\n  protected override readonly extractors = {\n    ingredients: this.ingredients.bind(this),\n    instructions: this.instructions.bind(this),\n  } satisfies ScraperExtractors\n\n  /**\n   * Parse ingredients from HTML using structured ingredients selectors.\n   * Filters out group headers that schema-org includes as ingredients.\n   */\n  protected ingredients(\n    prevValue: RecipeFields['ingredients'] | undefined,\n  ): RecipeFields['ingredients'] {\n    const headingSelector = '.structured-ingredients__list-heading'\n    const ingredientSelector = '.structured-ingredients__list-item'\n\n    if (prevValue && prevValue.length > 0) {\n      // Get values and filter out group headers that JSON-LD includes\n      const rawValues = flattenIngredients(prevValue)\n      const values = filterGroupHeaders(rawValues)\n\n      return groupIngredients(\n        this.$,\n        values,\n        headingSelector,\n        ingredientSelector,\n      )\n    }\n\n    throw new NoIngredientsFoundException()\n  }\n\n  /**\n   * Scrape and normalize each step under\n   * div.structured-project__steps > ol > li\n   */\n  protected instructions(): RecipeFields['instructions'] {\n    // select all <li> under the steps container\n    const items = this.$('div.structured-project__steps ol li').toArray()\n\n    if (items.length === 0) {\n      return []\n    }\n\n    const steps = items\n      .map((el) => {\n        // clone & strip images/figures before grabbing text\n        const $clone = this.$(el).clone()\n        $clone.find('img, picture, figure').remove()\n        return normalizeString($clone.text())\n      })\n      .filter((text) => text.length > 0)\n      .map(createInstructionItem)\n\n    return [createInstructionGroup(null, steps)]\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport { normalizeString } from '@/utils/parsing'\n\nexport class Skinnytaste extends AbstractScraper {\n  static host() {\n    return 'skinnytaste.com'\n  }\n\n  protected override readonly extractors = {\n    equipment: this.equipment.bind(this),\n  } satisfies ScraperExtractors\n\n  protected equipment(): RecipeFields['equipment'] {\n    const equipmentItems = this.$(\n      '.wprm-recipe-equipment-item .wprm-recipe-equipment-name',\n    )\n      .map((_, el) => normalizeString(this.$(el).text()))\n      .get()\n      .filter((item) => item.length > 0)\n\n    return new Set(equipmentItems)\n  }\n}\n","import { AbstractScraper, type ScraperExtractors } from '@/abstract-scraper'\nimport type { RecipeFields } from '@/types/recipe.interface'\nimport { normalizeString } from '@/utils/parsing'\n\nexport class TheCleverCarrot extends AbstractScraper {\n  static host() {\n    return 'theclevercarrot.com'\n  }\n\n  protected override readonly extractors = {\n    description: this.description.bind(this),\n  } satisfies ScraperExtractors\n\n  protected description(\n    prevValue: RecipeFields['description'] | undefined,\n  ): RecipeFields['description'] {\n    if (prevValue && normalizeString(prevValue)) {\n      return prevValue\n    }\n\n    const description =\n      this.$('meta[name=\"description\"]').attr('content') ??\n      this.$('meta[property=\"og:description\"]').attr('content') ??\n      this.$('.tasty-recipes-description-body').html() ??\n      null\n\n    const normalized = normalizeString(description)\n\n    if (!description || !normalized) {\n      throw new Error('Failed to extract description')\n    }\n\n    return description\n  }\n}\n","import { AbstractScraper } from '@/abstract-scraper'\nimport type { ScraperOptions } from '@/types/scraper.interface'\nimport { AmericasTestKitchen } from './americastestkitchen'\nimport { BBCGoodFood } from './bbcgoodfood'\nimport { BongEats } from './bongeats'\nimport { BrianLagerstrom } from './brianlagerstrom'\nimport { DamnDelicious } from './damndelicious'\nimport { Epicurious } from './epicurious'\nimport { InspiredTaste } from './inspiredtaste'\nimport { MyPlate } from './myplate'\nimport { NYTimes } from './nytimes'\nimport { OnceUponAChef } from './onceuponachef'\nimport { SimplyRecipes } from './simplyrecipes'\nimport { Skinnytaste } from './skinnytaste'\nimport { TheCleverCarrot } from './theclevercarrot'\n\n/**\n * Constructor type for scraper classes.\n */\ntype ScraperClass = {\n  new (html: string, url: string, options?: ScraperOptions): AbstractScraper\n  host(): string\n}\n\n/**\n * Scrapers with custom extraction logic.\n * Adding a new scraper only requires adding it to this list.\n */\nconst customScraperClasses = [\n  AmericasTestKitchen,\n  BBCGoodFood,\n  BongEats,\n  BrianLagerstrom,\n  DamnDelicious,\n  Epicurious,\n  InspiredTaste,\n  MyPlate,\n  SimplyRecipes,\n  NYTimes,\n  OnceUponAChef,\n  Skinnytaste,\n  TheCleverCarrot,\n] as const satisfies readonly ScraperClass[]\n\n/**\n * Hosts that can rely on generic schema.org extraction\n * and do not need dedicated scraper classes.\n */\nconst SCHEMA_ORG_ONLY_HOSTS = [\n  'addapinch.com',\n  'afarmgirlsdabbles.com',\n  'aflavorjournal.com',\n  'akispetretzikis.com',\n  'altonbrown.com',\n  'allrecipes.com',\n  'archanaskitchen.com',\n  'bestrecipes.com.au',\n  'blueapron.com',\n  'bonappetit.com',\n  'bowlofdelicious.com',\n  'brasspine.com',\n  'budgetbytes.com',\n  'eatingwell.com',\n  'chefjeanpierre.com',\n  'chewoutloud.com',\n  'familyfoodonthetable.com',\n  'food.com',\n  'halfbakedharvest.com',\n  'howtofeedaloon.com',\n  'inbloombakery.com',\n  'indianhealthyrecipes.com',\n  'joyfoodsunshine.com',\n  'lecremedelacrumb.com',\n  'maangchi.com',\n  'marmiton.org',\n  'marthastewart.com',\n  'natashaskitchen.com',\n  'noracooks.com',\n  'norecipes.com',\n  'organicallyaddison.com',\n  'recipetineats.com',\n  'savorynothings.com',\n  'seriouseats.com',\n  'simplegreensmoothies.com',\n  'sunbasket.com',\n  'sweetcsdesigns.com',\n  'tastesbetterfromscratch.com',\n  'tasty.co',\n  'tastyoven.com',\n  'thebigmansworld.com',\n  'thecookierookie.com',\n  'themediterraneandish.com',\n  'therecipecritic.com',\n  'unsophisticook.com',\n  'wellplated.com',\n  'zestfulkitchen.com',\n] as const satisfies readonly string[]\n\nfunction createSchemaOrgOnlyScraper(host: string): ScraperClass {\n  return class extends AbstractScraper {\n    static host() {\n      return host\n    }\n  }\n}\n\nconst schemaOrgOnlyScraperClasses = SCHEMA_ORG_ONLY_HOSTS.map((host) =>\n  createSchemaOrgOnlyScraper(host),\n)\n\n/**\n * Optional host aliases.\n * Example: 'bbc.co.uk': BBCGoodFood\n */\nconst scraperAliases = {\n  'bbc.co.uk': BBCGoodFood,\n} as const satisfies Record<string, ScraperClass>\n\nfunction buildScraperRegistry(\n  classes: readonly ScraperClass[],\n  aliases: Readonly<Record<string, ScraperClass>>,\n): Record<string, ScraperClass> {\n  const registry: Record<string, ScraperClass> = {}\n\n  const registerHost = (\n    host: string,\n    scraper: ScraperClass,\n    source: string,\n  ) => {\n    const existing = registry[host]\n\n    if (existing && existing !== scraper) {\n      throw new Error(`Duplicate scraper key '${host}' from ${source}.`)\n    }\n\n    registry[host] = scraper\n  }\n\n  for (const scraper of classes) {\n    registerHost(scraper.host(), scraper, 'host()')\n  }\n\n  for (const [alias, scraper] of Object.entries(aliases)) {\n    registerHost(alias, scraper, 'alias')\n  }\n\n  return registry\n}\n\n/**\n * A map of all scrapers keyed by host and aliases.\n */\nexport const scrapers = buildScraperRegistry(\n  [...customScraperClasses, ...schemaOrgOnlyScraperClasses],\n  scraperAliases,\n)\n","import { AbstractScraper } from '@/abstract-scraper'\nimport { getHostName } from '@/utils'\n\nexport class GenericScraper extends AbstractScraper {\n  static host() {\n    return '*'\n  }\n\n  protected override getHost(): string {\n    try {\n      return getHostName(this.url)\n    } catch {\n      return GenericScraper.host()\n    }\n  }\n}\n","import type { SafeParseResult } from './schema-adapter'\nimport { scrapers } from './scrapers/_index'\nimport { GenericScraper } from './scrapers/generic'\nimport type { RecipeObject } from './types/recipe.interface'\nimport type { ScraperOptions } from './types/scraper.interface'\nimport { getHostName } from './utils'\n\nexport * from '@/schemas/recipe.schema'\nexport * from '@/types/recipe.interface'\nexport * from '@/types/scraper.interface'\nexport * from './abstract-extractor-plugin'\nexport * from './abstract-postprocessor-plugin'\nexport * from './logger'\nexport * from './schema-adapter'\nexport * from './utils/parse-yields'\nexport { GenericScraper, scrapers }\n\nexport interface GetScraperOptions {\n  /**\n   * Return a generic schema.org scraper for unsupported hosts.\n   * @default false\n   */\n  wildMode?: boolean\n}\n\ninterface BaseScrapeRecipeOptions extends ScraperOptions {\n  /**\n   * Allow parsing unsupported hosts with GenericScraper fallback.\n   * @default true\n   */\n  wildMode?: boolean\n}\n\nexport interface ScrapeRecipeOptions extends BaseScrapeRecipeOptions {\n  /**\n   * Return a safe-parse result instead of throwing.\n   * @default false\n   */\n  safeParse?: false\n}\n\nexport interface ScrapeRecipeSafeParseOptions extends BaseScrapeRecipeOptions {\n  /**\n   * Return a safe-parse result instead of throwing.\n   */\n  safeParse: true\n}\n\n/**\n * Returns a scraper class for the given URL, if implemented.\n * Returns a GenericScraper if the host is not supported and `wildMode` is true.\n * Throws an error if the host is not supported and `wildMode` is false.\n */\nexport function getScraper(\n  url: string,\n  { wildMode = false }: GetScraperOptions = {},\n) {\n  const hostName = getHostName(url)\n  const scraper = scrapers[hostName]\n\n  if (scraper) {\n    return scraper\n  }\n\n  if (wildMode) {\n    return GenericScraper\n  }\n\n  throw new Error(\n    `The website '${hostName}' is not currently supported.\\nIf you want to help add support, please open an issue!`,\n  )\n}\n\n/**\n * Parse a recipe from HTML and URL in one call.\n * Falls back to generic schema.org extraction by default.\n */\nexport function scrapeRecipe(\n  html: string,\n  url: string,\n  options: ScrapeRecipeSafeParseOptions,\n): Promise<SafeParseResult<RecipeObject>>\nexport function scrapeRecipe(\n  html: string,\n  url: string,\n  options?: ScrapeRecipeOptions,\n): Promise<RecipeObject>\nexport async function scrapeRecipe(\n  html: string,\n  url: string,\n  {\n    safeParse = false,\n    wildMode = true,\n    ...scraperOptions\n  }: ScrapeRecipeOptions | ScrapeRecipeSafeParseOptions = {},\n): Promise<RecipeObject | SafeParseResult<RecipeObject>> {\n  const Scraper = getScraper(url, { wildMode })\n  const scraper = new Scraper(html, url, scraperOptions)\n  return safeParse ? scraper.safeParse() : scraper.parse()\n}\n"],"mappings":";;;;;;;AAAA,SAAgB,UAAa,OAAkC;CAC7D,OAAO,UAAU,KAAA;AACnB;AAEA,SAAgB,OAAU,OAAgC;CACxD,OAAO,UAAU;AACnB;AAGA,SAAgB,WAAW,OAAmC;CAC5D,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAgB,SAAS,OAAiC;CACxD,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAgB,cACd,OACkC;CAClC,OACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAO,eAAe,KAAK,MAAM,OAAO;AAE5C;AAEA,MAAa,gBACX,UAC0C;CAC1C,OAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAgB,SAAS,OAAiC;CACxD,OAAO,OAAO,UAAU;AAC1B;;;;;;AAOA,SAAgB,YAAY,OAAe;CACzC,IAAI;EACF,MAAM,EAAE,aAAa,IAAI,IAAI,KAAK;EAClC,OAAO,SAAS,WAAW,MAAM,IAAI,SAAS,MAAM,CAAC,IAAI;CAC3D,QAAQ;EACN,MAAM,IAAI,MAAM,gBAAgB,OAAO;CACzC;AACF;;;;AAKA,SAAgB,oBACd,OACA,iBAAiB,iBACT;CACR,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAGf,IAAI,aAAa,KAAK,KAAK,aAAa,SAAS,SAAS,MAAM,OAAO,GACrE,OAAO,MAAM;CAGf,IAAI,SAAS,KAAK,GAChB,OAAO;CAGT,OAAO;AACT;;;ACrEA,MAAM,oBAAoB;;;;;AAM1B,MAAa,WAAW,WAAmB,EAAE,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM;CACvE,MAAM,YAAY,MAAM,IAAI,MAAM;CAElC,OAAOA,IACJ,OAAO,GAAG,UAAU,kBAAkB,CAAC,CACvC,IAAI,KAAK,GAAG,UAAU,iBAAiB,CAAC,CACxC,IAAI,WAAW,GAAG,UAAU,qBAAqB,UAAU,YAAY,CAAC,CACxE,WAAW,MAAM,EAAE,KAAK,CAAC;AAC9B;;;;AAKA,MAAa,YAAY,cACvBA,IAAE,QAAQ,GAAG,UAAU,qBAAqB;;;;AAK9C,MAAa,oBAAoB,cAC/BA,IACG,IAAI,GAAG,UAAU,oBAAoB,CAAC,CACtC,SAAS,GAAG,UAAU,kBAAkB,CAAC,CACzC,SAAS;AAEd,MAAa,kBACX,QACA,cAEAA,IACG,MAAM,QAAQ,GAAG,UAAU,wBAAwB,CAAC,CACpD,IAAI,GAAG,GAAG,UAAU,mCAAmC;;;;;;;;;;ACvB5D,MAAa,wBAAwB;;;;;;AAOrC,MAAa,yBAAyBC,IAAE,OAAO;;CAE7C,UAAUA,IAAE,OAAO,CAAC,CAAC,SAAS;;;CAG9B,WAAWA,IAAE,OAAO,CAAC,CAAC,SAAS;;CAE/B,iBAAiBA,IAAE,OAAO,CAAC,CAAC,SAAS;;CAErC,eAAeA,IAAE,OAAO,CAAC,CAAC,SAAS;;CAEnC,aAAaA,IAAE,OAAO;;CAEtB,eAAeA,IAAE,QAAQ;AAC3B,CAAC;;;;AAKD,MAAa,uBAAuBA,IAAE,OAAO;CAC3C,OAAO,QAAQ,kBAAkB;;;;;CAKjC,QAAQ,uBAAuB,SAAS,CAAC,CAAC,SAAS;AACrD,CAAC;;;;AAKD,MAAa,wBAAwBA,IAAE,OAAO;CAC5C,MAAM,QAAQ,uBAAuB,CAAC,CAAC,SAAS;CAChD,OAAO,eAAe,sBAAsB,YAAY;AAC1D,CAAC;;;;;AAMD,MAAa,oBAAoBA,IAC9B,MAAM,uBAAuB,8BAA8B,CAAC,CAC5D,IAAI,GAAG,gDAAgD;;;;AAK1D,MAAa,wBAAwBA,IAAE,OAAO,EAC5C,OAAO,QAAQ,mBAAmB,EACpC,CAAC;;;;AAKD,MAAa,yBAAyBA,IAAE,OAAO;CAC7C,MAAM,QAAQ,wBAAwB,CAAC,CAAC,SAAS;CACjD,OAAO,eAAe,uBAAuB,aAAa;AAC5D,CAAC;;;;;AAMD,MAAa,qBAAqBA,IAC/B,MAAM,wBAAwB,+BAA+B,CAAC,CAC9D,IAAI,GAAG,iDAAiD;;;;AAK3D,MAAa,iBAAiBA,IAAE,OAAO,EACrC,OAAO,QAAQ,YAAY,EAC7B,CAAC;;;;AAKD,MAAa,kBAAkBA,IAAE,OAAO;CACtC,MAAM,QAAQ,iBAAiB,CAAC,CAAC,SAAS;CAC1C,OAAO,eAAe,gBAAgB,MAAM;AAC9C,CAAC;;;;;AAMD,MAAa,cAAcA,IACxB,MAAM,iBAAiB,wBAAwB,CAAC,CAChD,IAAI,GAAG,0CAA0C;;;;AAKpD,MAAa,aAAaA,IAAE,OAAO;CACjC,MAAM,SAAS,WAAW;CAC1B,MAAM,QAAQ,WAAW;AAC3B,CAAC;;;;;;;;;;;;;;;;;AAkBD,MAAa,yBAAyBA,IAAE,OAAO;CAE7C,eAAeA,IACZ,QAAQ,qBAAqB,CAAC,CAC9B,QAAQ,qBAAqB,CAAC,CAC9B,SAAS,2CAA2C;CAGvD,MAAMA,IAAE,SAAS,+BAA+B;CAEhD,OAAO,QAAQ,SAAS,EAAE,KAAK,IAAI,CAAC;CAEpC,QAAQ,QAAQ,UAAU,EAAE,KAAK,IAAI,CAAC;CAEtC,aAAa;CACb,cAAc;CACd,OAAO,YAAY,SAAS;CAG5B,cAAc,SAAS,eAAe;CACtC,OAAO,SAAS,OAAO;CAGvB,WAAW,iBAAiB,YAAY;CACxC,UAAU,iBAAiB,WAAW;CACtC,UAAU,iBAAiB,WAAW;CAGtC,SAASA,IACN,OAAO,0BAA0B,CAAC,CAClC,IAAI,GAAG,4BAA4B,CAAC,CACpC,IAAI,GAAG,2BAA2B,CAAC,CACnC,QAAQ,CAAC;CAEZ,cAAcA,IACX,IAAI,kCAAkC,CAAC,CACvC,YAAY,oCAAoC,CAAC,CACjD,QAAQ,CAAC;CAGZ,QAAQ,QAAQ,QAAQ;CACxB,aAAa,QAAQ,aAAa;CAElC,UAAU,QAAQ,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,IAAI;CAEjE,UAAU,QAAQ,WAAW,CAAC,CAAC,SAAS;CAExC,eAAe,QAAQ,gBAAgB,CAAC,CAAC,SAAS;CAGlD,UAAUA,IACP,MAAM,QAAQ,eAAe,GAAG,2BAA2B,CAAC,CAC5D,QAAQ,CAAC,CAAC;CAEb,SAASA,IACN,MAAM,QAAQ,cAAc,GAAG,0BAA0B,CAAC,CAC1D,QAAQ,CAAC,CAAC;CAEb,UAAUA,IACP,MAAM,QAAQ,cAAc,GAAG,2BAA2B,CAAC,CAC3D,QAAQ,CAAC,CAAC;CAEb,qBAAqBA,IAClB,MACC,QAAQ,0BAA0B,GAClC,uCACF,CAAC,CACA,QAAQ,CAAC,CAAC;CAEb,WAAWA,IACR,MAAM,QAAQ,gBAAgB,GAAG,4BAA4B,CAAC,CAC9D,QAAQ,CAAC,CAAC;CAEb,OAAOA,IAAE,MAAM,YAAY,wBAAwB,CAAC,CAAC,SAAS;CAG9D,WAAWA,IACR,OAAOA,IAAE,OAAO,GAAGA,IAAE,OAAO,GAAG,6BAA6B,CAAC,CAC7D,QAAQ,CAAC,CAAC;CAEb,SAASA,IACN,OAAOA,IAAE,OAAO,GAAGA,IAAE,OAAO,GAAG,2BAA2B,CAAC,CAC3D,QAAQ,CAAC,CAAC;AACf,CAAC;;;;;;;;;;;;;;;;;;AAmBD,SAAgB,uBAEd,QAAsB;CACtB,OAAO,OACJ,WAAW,SAAS;EAEnB,IAAI,CAAC,KAAK,aAAa,CAAC,OAAO,KAAK,QAAQ,KAAK,CAAC,OAAO,KAAK,QAAQ,GACpE,KAAK,YAAY,KAAK,WAAW,KAAK;EAExC,OAAO;CACT,CAAC,CAAC,CACD,QACE,EAAE,WAAW,UAAU,eAAe;EACrC,IAAI,CAAC,OAAO,SAAS,KAAK,CAAC,OAAO,QAAQ,KAAK,CAAC,OAAO,QAAQ,GAC7D,OAAO,aAAa,WAAW;EAEjC,OAAO;CACT,GACA;EACE,SACE;EACF,MAAM,CAAC,WAAW;CACpB,CACF,CAAC,CACA,QACE,SAAS;EACR,OAAO,KAAK,YAAY,KAAK,KAAK,eAAe;CACnD,GACA;EACE,SAAS;EACT,MAAM,CAAC,cAAc;CACvB,CACF;AACJ;;;;;;;;AASA,MAAa,qBAAqB,uBAAuB,sBAAsB;;;ACtR/E,IAAa,6BAAb,cAAgD,MAAM;CACxB;CAA5B,YAAY,OAA+B;EACzC,MAAM,iCAAiC,OAAO;EADpB,KAAA,QAAA;EAE1B,KAAK,OAAO;CACd;AACF;AAEA,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,QAAgB;EAC1B,MAAM,iCAAiC,QAAQ;EAC/C,KAAK,OAAO;CACd;AACF;AAEA,IAAa,4BAAb,cAA+C,MAAM;CACnD,YAAY,OAAe;EACzB,MAAM,uCAAuC,OAAO;EACpD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,4BAAb,cAA+C,MAAM;CAEjC;CACA;CAFlB,YACE,OACA,OACA;EACA,MAAM,MAAM,UAAU,KAAK,IACvB,sBAAsB,MAAM,KAAK,OAAO,KAAK,MAC7C,uBAAuB,MAAM;EAEjC,MAAM,GAAG;EAPO,KAAA,QAAA;EACA,KAAA,QAAA;EAOhB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,6BAAb,cAAgD,MAAM;CAElC;CACA;CACA;CAHlB,YACE,OACA,QACA,iBACA;EACA,MAAM,eAAe,oBACnB,iBACA,0BACF;EAEA,MACE,0CAA0C,MAAM,SAAS,OAAO,IAAI,cACtE;EAXgB,KAAA,QAAA;EACA,KAAA,SAAA;EACA,KAAA,kBAAA;EAUhB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,8BAAb,cAAiD,0BAA0B;CACzE,cAAc;EACZ,MAAM,aAAa;EACnB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,sBAAb,cAAyC,MAAM;CAE3B;CACA;CAFlB,YACE,QACA,iBACA;EACA,MAAM,0BAA0B;EAHhB,KAAA,SAAA;EACA,KAAA,kBAAA;EAGhB,KAAK,OAAO;CACd;AACF;;;ACvEA,IAAY,WAAL,yBAAA,UAAA;CACL,SAAA,SAAA,aAAA,KAAA;CACA,SAAA,SAAA,WAAA,KAAA;CACA,SAAA,SAAA,UAAA,KAAA;CACA,SAAA,SAAA,UAAA,KAAA;CACA,SAAA,SAAA,WAAA,KAAA;;AACF,EAAA,CAAA,CAAA;AAEA,IAAa,SAAb,MAAoB;CAER;CACA;CAFV,YACE,SACA,WAAQ,GACR;EAFQ,KAAA,UAAA;EACA,KAAA,WAAA;CACP;CAEH,QAAQ,GAAG,MAAiB;EAC1B,IAAI,KAAK,WAAA,GAA6B;EACtC,QAAQ,IAAI,aAAa,KAAK,QAAQ,IAAI,GAAG,IAAI;CACnD;CAEA,MAAM,GAAG,MAAiB;EACxB,IAAI,KAAK,WAAA,GAA2B;EACpC,QAAQ,MAAM,WAAW,KAAK,QAAQ,IAAI,GAAG,IAAI;CACnD;CAEA,IAAI,GAAG,MAAiB;EACtB,IAAI,KAAK,WAAA,GAA0B;EACnC,QAAQ,IAAI,UAAU,KAAK,QAAQ,IAAI,GAAG,IAAI;CAChD;CAEA,KAAK,GAAG,MAAiB;EACvB,IAAI,KAAK,WAAA,GAA0B;EACnC,QAAQ,KAAK,UAAU,KAAK,QAAQ,IAAI,GAAG,IAAI;CACjD;CAEA,KAAK,GAAG,MAAiB;EACvB,IAAI,KAAK,WAAA,GAA0B;EACnC,QAAQ,KAAK,UAAU,KAAK,QAAQ,IAAI,GAAG,IAAI;CACjD;CAEA,MAAM,GAAG,MAAiB;EAGxB,QAAQ,MAAM,WAAW,KAAK,QAAQ,IAAI,GAAG,IAAI;CACnD;AACF;;;ACzCA,IAAa,gBAAb,MAA2B;CACzB;CACA;CAEA,YACE,gBACA,oBACA,kBAAqC,CAAC,GACtC,sBAA6C,CAAC,GAC9C;EAGA,KAAK,mBAAmB,CAAC,GAAG,gBAAgB,GAAG,eAAe,CAAC,CAAC,MAC7D,GAAG,MAAM,EAAE,WAAW,EAAE,QAC3B;EAEA,KAAK,uBAAuB,CAC1B,GAAG,oBACH,GAAG,mBACL,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAC1C;CAEA,gBAAgB;EACd,OAAO,KAAK;CACd;CAEA,oBAAoB;EAClB,OAAO,KAAK;CACd;AACF;;;AC9BA,IAAsB,sBAAtB,MAA0C,CAU1C;;;;;;ACPA,SAAgB,gBAAgB,KAAwC;CACtE,OACE,KACI,KAAK,CAAC,CAEP,QAAQ,QAAQ,GAAG,CAAC,CAEpB,QAAQ,SAAS,GAAG,KAAK;AAEhC;AAEA,SAAgB,mBAAmB,OAAuB;CACxD,OAAO,gBAAgB,MAAM,QAAQ,wBAAwB,EAAE,CAAC;AAClE;AAEA,SAAgB,YACd,OACA,WACU;CACV,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,MAAM,QAAkB,CAAC;CAEzB,KAAK,MAAM,QAAQ,MAAM,MAAM,SAAS,GAAG;EACzC,MAAM,MAAM,gBAAgB,IAAI;EAEhC,IAAI,KACF,MAAM,KAAK,GAAG;CAElB;CAEA,OAAO;AACT;AAEA,SAAS,0BAA0B,OAA8B;CAC/D,MAAM,aAAa,gBAAgB,KAAK,CAAC,CAAC,YAAY;CAEtD,IAAI,CAAC,YACH,OAAO;CAGT,MAAM,UAAU,MAAM,KACpB,WAAW,SACT,2FACF,CACF;CAEA,IAAI,QAAQ,WAAW,GACrB,OAAO;CAGT,IAAI,eAAe;CAEnB,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,SAAS,OAAO,WAAW,MAAM,MAAM,EAAE;EAC/C,MAAM,OAAO,MAAM,MAAM;EAEzB,IAAI,OAAO,MAAM,MAAM,GACrB;EAGF,IAAI,cAAc,KAAK,IAAI,GAAG;GAC5B,gBAAgB,SAAS,KAAK;GAC9B;EACF;EAEA,IAAI,2BAA2B,KAAK,IAAI,GAAG;GACzC,gBAAgB,SAAS;GACzB;EACF;EAEA,IAAI,+BAA+B,KAAK,IAAI,GAAG;GAC7C,gBAAgB;GAChB;EACF;EAEA,IAAI,+BAA+B,KAAK,IAAI,GAC1C,gBAAgB,SAAS;CAE7B;CAEA,OAAO,eAAe,IAAI,KAAK,MAAM,YAAY,IAAI;AACvD;;;;AAKA,SAAgB,aAAa,OAAe;CAC1C,IAAI;EAEF,MAAM,eAAe,UADJC,MAAc,KACO,CAAC;EACvC,OAAO,KAAK,MAAM,eAAe,EAAE;CACrC,SAAS,OAAO;EACd,MAAM,uBAAuB,0BAA0B,KAAK;EAE5D,IAAI,yBAAyB,MAC3B,OAAO;EAGT,MAAM;CACR;AACF;;;ACjGA,MAAM,6BAA6B;CACjC,MAAM;EACJ,kBAAkB,CAChB,oCACA,yBACF;EACA,eAAe,CAAC,2BAA2B,6BAA6B;CAC1E;CACA,OAAO;EACL,kBAAkB,CAChB,4CACA,+BACF;EACA,eAAe,CACb,yCACA,kCACF;CACF;AACF;;;;AAQA,SAAgB,qBAAqB,OAA+B;CAClE,OAAO,EAAE,MAAM;AACjB;;;;AAKA,SAAgB,sBACd,MACA,QAA0B,CAAC,GACV;CACjB,OAAO;EAAE;EAAM;CAAM;AACvB;;;;AAKA,SAAgB,iBAAiB,OAAyC;CACxE,OAAO,cAAc,KAAK,KAAK,WAAW,SAAS,SAAS,MAAM,KAAK;AACzE;;;;AAKA,SAAgB,kBAAkB,OAA0C;CAC1E,OACE,cAAc,KAAK,KACnB,UAAU,SACV,WAAW,SACX,MAAM,QAAQ,MAAM,KAAK,KACzB,MAAM,MAAM,MAAM,gBAAgB;AAEtC;;;;AAKA,SAAgB,cAAc,OAAsC;CAClE,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,iBAAiB;AAC9D;;;;;AAMA,SAAgB,mBAAmB,aAAoC;CACrE,OAAO,YAAY,SAAS,UAAU,MAAM,MAAM,KAAK,SAAS,KAAK,KAAK,CAAC;AAC7E;;;;;AAMA,SAAgB,qBACd,QACA,YAA2B,MACd;CAEb,OAAO,CAAC,sBAAsB,WADhB,OAAO,IAAI,oBACoB,CAAC,CAAC;AACjD;AAEA,SAAgB,wBAAwB,OAAe,QAAwB;CAC7E,IAAI,UAAU,QACZ,OAAO;CAGT,IAAI,MAAM,SAAS,KAAK,OAAO,SAAS,GACtC,OAAO;CAGT,MAAM,WAAW,MACf,IAAI,IAAI,MAAM,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;CAE3E,MAAM,eAAe,QAAQ,KAAK;CAClC,MAAM,gBAAgB,QAAQ,MAAM;CAMpC,OAAQ,IAJiB,CAAC,GAAG,YAAY,CAAC,CAAC,QAAQ,MACjD,cAAc,IAAI,CAAC,CACrB,CAAC,CAAC,UAE+B,aAAa,OAAO,cAAc;AACrE;AAEA,SAAgB,UAAU,YAAoB,eAAiC;CAC7E,IAAI,cAAc,WAAW,GAC3B,MAAM,IAAI,MAAM,+BAA+B;CAGjD,MAAM,SAAS,cAAc,KAAK,MAChC,wBAAwB,YAAY,CAAC,CACvC;CAEA,IAAI,YAAY;CAChB,IAAI,YAAY,OAAO;CAEvB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,OAAO,KAAK,WAAW;EACzB,YAAY,OAAO;EACnB,YAAY;CACd;CAGF,OAAO,cAAc;AACvB;AAEA,SAAS,cACP,GACA,gBACA,aACyB;CACzB,IAAI,kBAAkB,aAAa;EAEjC,IAAI,EAAE,cAAc,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,QAC7C,OAAO,CAAC,gBAAgB,WAAW;EAGrC,OAAO;CACT;CAEA,MAAM,SAAS,OAAO,OAAO,0BAA0B;CAEvD,KAAK,MAAM,EAAE,kBAAkB,mBAAmB,QAChD,KAAK,MAAM,WAAW,kBACpB,KAAK,MAAM,QAAQ,eACjB,IAAI,EAAE,OAAO,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,QAC/B,OAAO,CAAC,SAAS,IAAI;CAM7B,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,iBACd,GACA,kBACA,iBACA,cACa;CACb,MAAM,YAAY,cAAc,GAAG,iBAAiB,YAAY;CAEhE,IAAI,CAAC,WACH,OAAO,qBAAqB,gBAAgB;CAG9C,MAAM,CAAC,mBAAmB,sBAAsB;CAEhD,MAAM,mBAAmB,EAAE,kBAAkB,CAAC,CAC3C,QAAQ,CAAC,CACT,KAAK,OAAO,gBAAgB,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAC1C,QAAQ,SAAS,KAAK,SAAS,CAAC;CAEnC,MAAM,yBAAyB,IAAI,IAAI,gBAAgB;CACvD,MAAM,yBAAyB,IAAI,IACjC,iBAAiB,KAAK,UAAU,gBAAgB,KAAK,CAAC,CACxD;CAMA,IACE,iBAAiB,SAAS,iBAAiB,UAC3C,uBAAuB,OAAO,uBAAuB,MAErD,OAAO,qBAAqB,gBAAgB;CAG9C,MAAM,4BAAY,IAAI,IAA6B;CACnD,IAAI,iBAAgC;CAGpC,MAAM,WAAW,EAAE,GAAG,kBAAkB,IAAI,oBAAoB,CAAC,CAAC,QAAQ;CAE1E,KAAK,MAAM,MAAM,UAAU;EACzB,MAAM,MAAM,EAAE,EAAE;EAEhB,IAAI,IAAI,GAAG,iBAAiB,GAAG;GAG7B,iBADoB,gBAAgB,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,MAAM,EACnC,KAAK;GAEhC,IAAI,CAAC,UAAU,IAAI,cAAc,GAC/B,UAAU,IAAI,gBAAgB,CAAC,CAAC;EAEpC,OAAO,IAAI,IAAI,GAAG,kBAAkB,GAAG;GAErC,MAAM,OAAO,gBAAgB,IAAI,KAAK,CAAC;GAEvC,IAAI,CAAC,MACH;GAGF,MAAM,UAAU,UAAU,MAAM,gBAAgB;GAChD,MAAM,UAAU,kBAAkB;GAElC,IAAI,CAAC,UAAU,IAAI,OAAO,GACxB,UAAU,IAAI,SAAS,CAAC,CAAC;GAG3B,UAAU,IAAI,OAAO,CAAC,EAAE,KAAK,OAAO;EACtC;CACF;CAGA,MAAM,SAAsB,CAAC;CAE7B,KAAK,MAAM,CAAC,MAAM,UAAU,UAAU,QAAQ,GAC5C,OAAO,KAAK,sBAAsB,MAAM,MAAM,IAAI,oBAAoB,CAAC,CAAC;CAG1E,MAAM,iBAAiB,OAAO,QAAQ,UAAU,MAAM,MAAM,SAAS,CAAC;CAEtE,IAAI,eAAe,SAAS,GAC1B,OAAO;CAGT,OAAO,iBAAiB,SAAS,IAC7B,qBAAqB,gBAAgB,IACrC,CAAC;AACP;;;;;;AChQA,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;AACF;;;;AAKA,SAAgB,sBAAsB,OAAgC;CACpE,OAAO,EAAE,MAAM;AACjB;;;;AAKA,SAAgB,uBACd,MACA,QAA2B,CAAC,GACV;CAClB,OAAO;EAAE;EAAM;CAAM;AACvB;;;;AAKA,SAAgB,kBAAkB,OAA0C;CAC1E,OAAO,cAAc,KAAK,KAAK,WAAW,SAAS,SAAS,MAAM,KAAK;AACzE;;;;AAKA,SAAgB,mBAAmB,OAA2C;CAC5E,OACE,cAAc,KAAK,KACnB,UAAU,SACV,WAAW,SACX,MAAM,QAAQ,MAAM,KAAK,KACzB,MAAM,MAAM,MAAM,iBAAiB;AAEvC;;;;AAKA,SAAgB,eAAe,OAAuC;CACpE,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,kBAAkB;AAC/D;;;;AAyBA,SAAgB,yBAAyB,OAAe;CACtD,KAAK,MAAM,WAAW,sBAAsB;EAC1C,MAAM,QAAQ,IAAI,OAAO,QAAQ,QAAQ,aAAa,GAAG;EACzD,IAAI,MAAM,KAAK,KAAK,GAClB,OAAO,MAAM,QAAQ,OAAO,EAAE;CAElC;CACA,OAAO;AACT;AAEA,MAAM,iBAAiB;AACvB,MAAM,0BAA0B;;;;;AAMhC,SAAgB,kBAAkB,OAAe;CAC/C,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,MAAM,UAAU,yBAAyB,KAAK,CAAC,CAAC,KAAK;CAGrD,IAAI,QAAQ,YAAY,SAAS,cAAc;CAG/C,IAAI,MAAM,WAAW,GACnB,QAAQ,YAAY,SAAS,uBAAuB;CAGtD,OAAO;AACT;AAEA,MAAM,sBAAsB;;;;;AAM5B,SAAgB,0BAA0B,OAAyB;CACjE,MAAM,aAAa,gBAAgB,KAAK;CACxC,MAAM,UAAU,MAAM,KAAK,WAAW,SAAS,mBAAmB,CAAC;CAEnE,IAAI,QAAQ,WAAW,GACrB,OAAO,aAAa,CAAC,UAAU,IAAI,CAAC;CAGtC,MAAM,QAAkB,CAAC;CACzB,MAAM,aAAa,QAAQ;CAE3B,IAAI,cAAc,WAAW,QAAQ,GAAG;EACtC,MAAM,SAAS,gBAAgB,WAAW,MAAM,GAAG,WAAW,KAAK,CAAC;EACpE,IAAI,QACF,MAAM,KAAK,MAAM;CAErB;CAEA,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,QAAQ,QAAQ;EACtB,MAAM,YAAY,QAAQ,IAAI;EAE9B,IAAI,CAAC,OACH;EAGF,MAAM,QAAQ,MAAM,QAAQ,MAAM,EAAE,CAAC;EACrC,MAAM,MAAM,YAAY,UAAU,QAAQ,WAAW;EACrD,MAAM,OAAO,gBAAgB,WAAW,MAAM,OAAO,GAAG,CAAC;EAEzD,IAAI,MACF,MAAM,KAAK,IAAI;CAEnB;CAEA,OAAO;AACT;;;ACzJA,IAAa,qBAAb,cAAwC,oBAAoB;CAC1D,OAAO;CACP,WAAW;CAEX,kBAAkD;EAChD;EACA;EACA;EACA;CACF;CAEA,cAA8C,OAAqB;EACjE,OAAO,KAAK,gBAAgB,SAAS,KAAK;CAC5C;CAEA,QAAW,OAA2B,OAAa;EACjD,IAAI,CAAC,KAAK,cAAc,KAAK,GAC3B,OAAO;EAGT,IAAI,SAAS,KAAK,GAChB,OAAO,KAAK,UAAU,KAAK;EAG7B,IAAI,UAAU,kBAAkB,eAAe,KAAK,GAClD,OAAO,KAAK,oBAAoB,KAAK;EAGvC,IAAI,UAAU,iBAAiB,cAAc,KAAK,GAChD,OAAO,KAAK,mBAAmB,KAAK;EAGtC,OAAO;CACT;CAEA,mBAA2B,aAAuC;EAChE,OAAO,YAAY,KAAK,WAAW;GACjC,MAAM,MAAM,SAAS,OAAO,OAAO,KAAK,UAAU,MAAM,IAAI;GAC5D,OAAO,MAAM,MAAM,KAAK,UAAU,EAChC,OAAO,KAAK,UAAU,KAAK,KAAK,EAClC,EAAE;EACJ,EAAE;CACJ;CAEA,oBAA4B,cAA0C;EACpE,OAAO,aAAa,KAAK,WAAW;GAClC,MAAM,MAAM,SAAS,OAAO,OAAO,KAAK,UAAU,MAAM,IAAI;GAC5D,OAAO,MAAM,MAAM,KAAK,UAAU,EAChC,OAAO,KAAK,UAAU,KAAK,KAAK,EAClC,EAAE;EACJ,EAAE;CACJ;CAEA,UAAkB,MAAsB;EAItC,OAHU,KAAK,MAAM,MAAM,KAGpB,CAAC,CAAC,KAAK,CAAC,CACZ,KAAK,CAAC,CACN,QAAQ,WAAW,GAAG,CAAC,CACvB,KAAK;CACV;AACF;;;;;;;;;ACvDA,IAAa,yBAAb,cAA4C,oBAAoB;CAIjC;CAH7B,OAAO;CACP,WAAW;CAEX,YAAY,UAAmD,CAAC,GAAG;EACjE,MAAM;EADqB,KAAA,UAAA;CAE7B;CAEA,cAA8C,OAAqB;EACjE,OAAO,UAAU;CACnB;CAEA,QAAW,OAA2B,OAAa;EACjD,IAAI,CAAC,KAAK,cAAc,KAAK,GAC3B,OAAO;EAGT,IAAI,cAAc,KAAK,GACrB,OAAO,KAAK,mBAAmB,KAAK;EAGtC,OAAO;CACT;CAEA,mBAA2B,aAAuC;EAChE,OAAO,YAAY,KAAK,WAAW;GACjC,MAAM,MAAM;GACZ,OAAO,MAAM,MAAM,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC;EACvD,EAAE;CACJ;CAEA,UAAkB,MAAsC;EAKtD,MAAM,mBAJS,gBAAgB,KAAK,OAAO,KAAK,OAIlB,CAAC,CAAC,MAAM;EAEtC,OAAO;GACL,OAAO,KAAK;GACZ,QAAQ;EACV;CACF;AACF;;;ACxDA,IAAsB,iBAAtB,MAAqC;CAOd;CAArB,YAAY,GAAwB;EAAf,KAAA,IAAA;CAAgB;AACvC;;;ACPA,IAAsB,kBAAtB,cAA8C,eAAe,CAY7D;;;ACRA,IAAa,qBAAb,cAAwC,0BAA0B;CAChE,YAAY,MAAc;EACxB,MAAM,IAAI;EACV,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,MAAa,wBAAwB,gBAAgB;CACnD,OAAO,gBAAgB;CACvB,WAAW;CAEX,aAEI;EACF,OAAO,KAAK,MAAM,KAAK,IAAI;EAC3B,UAAU,KAAK,SAAS,KAAK,IAAI;CACnC;CAEA,SAAS,OAA2B;EAClC,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,SAAS,KAAK;CACpD;CAEA,QAAwC,OAA+B;EACrE,MAAM,YAAY,KAAK,WAAW;EAElC,IAAI,CAAC,WACH,MAAM,IAAI,2BAA2B,KAAK;EAG5C,OAAO,UAAU;CACnB;CAEA,WAAmB;EACjB,MAAM,OACJ,KAAK,EAAE,iCAA+B,CAAC,CAAC,KAAK,SAAS,KACtD,KAAK,EAAE,6BAA2B,CAAC,CAAC,KAAK,SAAS;EAEpD,IAAI,CAAC,MACH,MAAM,IAAI,mBAAmB,UAAU;EAGzC,OAAO;CACT;CAEA,QAAgB;EACd,MAAM,QAAQ,KAAK,EAAE,sCAAoC,CAAC,CAAC,KAAK,SAAS;EAEzE,IAAI,CAAC,OAAO,WAAW,MAAM,GAC3B,MAAM,IAAI,mBAAmB,OAAO;EAGtC,OAAO;CACT;AACF;;;ACvDA,SAAS,uBAAuB,OAAuB;CACrD,QAAQ,OAAR;EACE,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,SAEE,OAAO,MADM,MAAM,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAC9B,CAAC,CAAC,SAAS,GAAG,GAAG;CAErC;AACF;;;;;;;AAQA,SAAgB,qCAAqC,KAAqB;CACxE,IAAI,WAAW;CACf,IAAI,WAAW;CACf,IAAI,aAAa;CAEjB,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,CAAC,UAAU;GACb,YAAY;GAEZ,IAAI,SAAS,MACX,WAAW;GAEb;EACF;EAEA,IAAI,YAAY;GACd,YAAY;GACZ,aAAa;GACb;EACF;EAEA,IAAI,SAAS,MAAM;GACjB,YAAY;GACZ,aAAa;GACb;EACF;EAEA,IAAI,SAAS,MAAK;GAChB,YAAY;GACZ,WAAW;GACX;EACF;EAEA,IAAI,KAAK,WAAW,CAAC,KAAK,IAAM;GAC9B,YAAY,uBAAuB,IAAI;GACvC;EACF;EAEA,YAAY;CACd;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,oBAAoB,KAA8B;CAChE,IAAI;EACF,OAAO;GAAE,MAAM,KAAK,MAAM,GAAG;GAAG,UAAU;EAAM;CAClD,SAAS,OAAO;EACd,MAAM,cAAc,qCAAqC,GAAG;EAE5D,IAAI,gBAAgB,KAClB,MAAM;EAGR,IAAI;GACF,OAAO;IAAE,MAAM,KAAK,MAAM,WAAW;IAAG,UAAU;GAAK;EACzD,QAAQ;GACN,MAAM;EACR;CACF;AACF;;;ACvFA,MAAM,eACJ,KACA,KACA,UACG;CACH,IAAI,IAAI,SAAS,KAAA,GACf,IAAI,OAAO;MACN,IAAI,MAAM,QAAQ,IAAI,IAAI,GAC/B,IAAI,IAAI,CAAC,KAAK,KAAK;MAEnB,IAAI,OAAO,CAAC,IAAI,MAAM,KAAK;AAE/B;AAEA,MAAM,2BACJ,YACuB;CACvB,IAAI,QAAQ,GAAG,MAAM,GACnB,OAAO,QAAQ,KAAK,SAAS;CAG/B,IAAI,QAAQ,GAAG,MAAM,GACnB,OAAO,QAAQ,KAAK,UAAU,KAAK,QAAQ,KAAK,CAAC,CAAC,KAAK;CAGzD,IAAI,QAAQ,GAAG,KAAK,GAClB,OAAO,QAAQ,KAAK,KAAK;CAG3B,IAAI,QAAQ,GAAG,GAAG,GAChB,OAAO,QAAQ,KAAK,MAAM;CAG5B,OAAO,QAAQ,KAAK,CAAC,CAAC,KAAK;AAC7B;AAEA,MAAM,qBAAqB,aAAyC;CAElE,OADkB,SAAS,MAAM,oBAClB,CAAC,GAAG;AACrB;;;;;;;;AASA,SAAgB,iBACd,GACA,UACmB;CACnB,MAAM,UAA6B,CAAC;CAGpC,EAFmB,QAEZ,CAAC,CAAC,MAAM,GAAG,OAAO;EACvB,MAAM,WAAW,EAAE,EAAE;EACrB,MAAM,WAAW,SAAS,KAAK,UAAU;EACzC,MAAM,aAA8B,CAAC;EAGrC,IAAI,UAAU;GACZ,MAAM,aAAa,kBAAkB,QAAQ;GAC7C,IAAI,YACF,WAAW,WAAW;EAE1B;EAGA,MAAM,WAAW,SAAS,KAAK,YAAY,CAAC,CAAC,QAAQ,YAAY;EACjE,MAAM,kBAAkB,SAAS,KAAK,YAAY;EAmBlD,SAhBgC,QAAQ,GAAG,WAAW;GACpD,MAAM,QAAQ,EAAE,MAAiB;GAGjC,IAAI,MAAM,KAAK,UAAU,GACvB,OAAO;GAQT,OAAO,CAJoB,gBACxB,QAAQ,CAAC,CACT,MAAM,aAAa,EAAE,QAAmB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS,CAEzC;EAC3B,CAEa,CAAC,CAAC,MAAM,GAAG,WAAW;GACjC,MAAM,QAAQ,EAAE,MAAiB;GACjC,MAAM,WAAW,MAAM,KAAK,UAAU;GACtC,IAAI,CAAC,UAAU;GAEf,IAAI;GAGJ,MAAM,iBAAiB,MAAM,KAAK,UAAU;GAC5C,IAAI,gBAAgB;IAClB,MAAM,eAAgC,CAAC;IAGvC,MAAM,mBAAmB,kBAAkB,cAAc;IACzD,IAAI,kBACF,aAAa,WAAW;IAI1B,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM,GAAG,aAAa;KAC7C,MAAM,UAAU,EAAE,QAAmB;KACrC,MAAM,aAAa,QAAQ,KAAK,UAAU;KAC1C,IAAI,CAAC,YAAY;KAEjB,MAAM,cAAc,wBAAwB,OAAO;KACnD,IAAI,eAAe,gBAAgB,IACjC,YAAY,cAAc,YAAY,WAAW;IAErD,CAAC;IAED,YAAY;GACd,OAEE,YAAY,wBAAwB,KAAK;GAG3C,IAAI,cAAc,KAAA,KAAa,cAAc,IAC3C,YAAY,YAAY,UAAU,SAAS;EAE/C,CAAC;EAGD,IACE,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,KAChC,OAAO,KAAK,UAAU,CAAC,CAAC,WAAW,KAAK,CAAC,WAAW,UAErD,QAAQ,KAAK,UAAU;CAE3B,CAAC;CAED,OAAO;AACT;;;;;;;AAQA,SAAgB,uBAAuB,GAAkC;CACvE,OAAO,iBACL,GACA,2DACF;AACF;;;ACjKA,MAAM,qBAAqB;AAE3B,MAAM,oBACJ;AAEF,MAAM,qBAAyC;CAC7C,CAAC,SAAS,OAAO;CACjB,CAAC,SAAS,SAAS;CACnB,CAAC,QAAQ,OAAO;CAChB,CAAC,YAAY,YAAY;CACzB,CAAC,OAAO,MAAM;CACd,CAAC,UAAU,SAAS;CACpB,CAAC,UAAU,SAAS;CACpB,CAAC,WAAW,UAAU;CACtB,CAAC,QAAQ,QAAQ;CACjB,CAAC,OAAO,MAAM;CACd,CAAC,OAAO,MAAM;CACd,CAAC,QAAQ,OAAO;CAChB,CAAC,UAAU,SAAS;CACpB,CAAC,SAAS,QAAQ;CAClB,CAAC,SAAS,QAAQ;CAClB,CAAC,QAAQ,OAAO;CAChB,CAAC,SAAS,QAAQ;CAClB,CAAC,SAAS,QAAQ;CAClB,CAAC,SAAS,QAAQ;CAClB,CAAC,SAAS,QAAQ;CAClB,CAAC,OAAO,MAAM;CACd,CAAC,SAAS,SAAS;CACnB,CAAC,iBAAiB,gBAAgB;CAClC,CAAC,WAAW,UAAU;CACtB,CAAC,QAAQ,OAAO;AAElB;;;;;;;;;;;;AAaA,SAAgB,YAAY,SAAyB;CACnD,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,qBAAqB;CAGvC,MAAM,YAAY;CAGlB,MAAM,UADQ,UAAU,MAAM,kBACV,CAAC,EAAE,QAAQ,SAAS;CAExC,MAAM,iBAAiB,UAAU,YAAY;CAC7C,IAAI,YAA2B;CAC/B,IAAI,kBAAkB;CAEtB,KAAK,MAAM,CAAC,UAAU,WAAW,oBAC/B,IAAI,eAAe,SAAS,QAAQ,KAAK,eAAe,SAAS,MAAM,GAAG;EACxE,MAAM,cAAc,eAAe,SAAS,QAAQ,IAChD,SAAS,SACT,OAAO;EACX,IAAI,cAAc,iBAAiB;GACjC,kBAAkB;GAClB,YAAY,GAAG,QAAQ,GAAG,OAAO,WAAW,OAAO,MAAM,IAAI,WAAW;EAC1E;CACF;CAKF,IAAI,WAAW;EACb,MAAM,aAAa,UAAU,MAAM,QAAQ;EAE3C,IAAI,YAEF,aAAa,IAAI,WAAW;EAE9B,OAAO;CACT;CAEA,MAAM,SACJ,OAAO,WAAW,OAAO,IAAI,KAAK,OAAO,WAAW,OAAO,MAAM,IAC7D,MACA;CAEN,IAAI,kBAAkB,KAAK,SAAS,GAClC,OAAO,GAAG,QAAQ,OAAO;CAG3B,OAAO,GAAG,QAAQ,UAAU;AAC9B;;;AC5EA,SAAgB,YAAY,KAA4B;CACtD,OAAO,cAAc,GAAG,KAAK,YAAY,OAAO,MAAM,QAAQ,IAAI,SAAS;AAC7E;AAEA,SAAgB,WAAW,KAA0C;CACnE,OACE,cAAc,GAAG,KACjB,WAAW,QACV,SAAS,IAAI,QAAQ,KAAK,MAAM,QAAQ,IAAI,QAAQ;AAEzD;AAEA,SAAgB,gBAAgB,KAAoC;CAClE,OAAO,YAAY,GAAG,KAAK,WAAW,GAAG;AAC3C;AAEA,SAAgB,YACd,KACA,MAC6B;CAC7B,IAAI,CAAC,WAAW,GAAG,GAAG,OAAO;CAI7B,QAFkB,MAAM,QAAQ,IAAI,QAAQ,IAAI,IAAI,QAAQ,CAAC,KAAK,IAAI,cAEjD;AACvB;AAEA,SAAgB,kBAAkB,KAAsC;CACtE,OAAO,YAAY,KAAK,iBAAiB;AAC3C;AAEA,SAAgB,eAAe,KAAmC;CAChE,OAAO,YAAY,KAAK,cAAc;AACxC;AAEA,SAAgB,YAAY,KAAgC;CAC1D,OAAO,YAAY,KAAK,WAAW;AACrC;AAEA,SAAgB,eAAe,KAAmC;CAChE,OAAO,YAAY,KAAK,cAAc;AACxC;AAEA,SAAgB,SAAS,KAA6B;CACpD,OAAO,YAAY,KAAK,QAAQ;AAClC;AAEA,SAAgB,SAAS,KAA6B;CACpD,OAAO,YAAY,KAAK,QAAQ;AAClC;AAMA,SAAgB,UAAU,KAA8B;CACtD,OAAO,YAAY,KAAK,SAAS;AACnC;AAEA,SAAgB,UAAU,KAA8B;CACtD,OAAO,YAAY,KAAK,SAAS;AACnC;;;AC/BA,IAAa,qBAAb,cAAwC,0BAA0B;CAChE,YAAY,OAAe,OAAiB;EAC1C,MAAM,OAAO,KAAK;EAClB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,gCAAb,cAAmD,MAAM;CAErC;CACA;CAFlB,YACE,OACA,aACA;EACA,MAAM,aAAa,YAAY;EAC/B,MAAM,eAAe,oBACnB,YACA,yBACF;EAEA,MACE,6CAA6C,MAAM,KAAK,cAC1D;EAXgB,KAAA,QAAA;EACA,KAAA,cAAA;EAWhB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,MAAa,wBAAwB,gBAAgB;CACnD,OAAO,gBAAgB;CAGvB,WAAW;CAEX;CACA,aAAsC,CAAC;CACvC,SAA+B,EAAE,SAAS,SAAS;CACnD,SAAyC,CAAC;CAC1C,cAAuD,CAAC;CACxD,cAAqC;CACrC,oBAAuC,CAAC;CACxC,kBAA0B;CAE1B,aAEI;EACF,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,OAAO,KAAK,MAAM,KAAK,IAAI;EAC3B,QAAQ,KAAK,OAAO,KAAK,IAAI;EAC7B,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,OAAO,KAAK,MAAM,KAAK,IAAI;EAC3B,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,cAAc,KAAK,aAAa,KAAK,IAAI;EACzC,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,QAAQ,KAAK,OAAO,KAAK,IAAI;EAC7B,WAAW,KAAK,UAAU,KAAK,IAAI;EACnC,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,SAAS,KAAK,QAAQ,KAAK,IAAI;EAC/B,eAAe,KAAK,cAAc,KAAK,IAAI;EAC3C,SAAS,KAAK,QAAQ,KAAK,IAAI;EAC/B,cAAc,KAAK,aAAa,KAAK,IAAI;EACzC,WAAW,KAAK,UAAU,KAAK,IAAI;EACnC,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,qBAAqB,KAAK,oBAAoB,KAAK,IAAI;CACzD;CAEA,YAAY,GAAe,UAAqB;EAC9C,MAAM,CAAC;EAEP,KAAK,SAAS,IAAI,OAAO,gBAAgB,MAAM,QAAQ;EACvD,KAAK,kBAAkB;EACvB,KAAK,qBAAqB;EAC1B,KAAK,kBAAkB;CACzB;CAEA,SAAS,OAAoC;EAC3C,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,SAAS,KAAK;CACpD;CAEA,QAAwC,OAA+B;EACrE,MAAM,YAAY,KAAK,WAAW;EAElC,IAAI,CAAC,WAAW,SAAS,GACvB,MAAM,IAAI,0BAA0B,KAAK;EAG3C,IAAI;GACF,OAAO,UAAU;EACnB,SAAS,OAAO;GACd,IACE,iBAAiB,sBACjB,KAAK,gCAAgC,KAAK,GAE1C,MAAM,IAAI,8BAA8B,OAAO,KAAK,iBAAiB;GAGvE,MAAM;EACR;CACF;;;;CAKA,oBAA4B;EAC1B,KAAK,EAAE,sCAAoC,CAAC,CAAC,MAAM,GAAG,OAAO;GAC3D,IAAI;IACF,MAAM,OAAO,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK;IAErC,IAAI,MAAM;KACR,MAAM,EAAE,MAAM,aAAa,oBAAoB,IAAI;KAEnD,IAAI,UACF,KAAK,OAAO,MACV,6EACF;KAGF,IAAI,MAAM,QAAQ,IAAI;WACf,MAAM,QAAQ,MACjB,IAAI,gBAAgB,IAAI,GACtB,KAAK,WAAW,KAAK,IAAI;KAAA,OAGxB,IAAI,gBAAgB,IAAI,GAC7B,KAAK,WAAW,KAAK,IAAI;IAE7B;GACF,SAAS,OAAO;IACd,KAAK,OAAO,KAAK,2BAA2B,KAAK;IACjD,KAAK,kBAAkB,KAAK,KAAK;GACnC;EACF,CAAC;CACH;;;;CAKA,uBAA+B;EAC7B,MAAM,mBAAmB,uBAAuB,KAAK,CAAC;EAEtD,KAAK,MAAM,OAAO,kBAChB,KAAK,WAAW,KAAK,GAAoB;CAE7C;CAEA,eAAuB,KAAc,OAAqC;EACxE,IAAI,CAAC,cAAc,GAAG,GAAG,OAAO,KAAA;EAEhC,KAAK,MAAM,QAAQ,OACjB,IAAI,SAAS,IAAI,KAAK,GACpB,OAAO,IAAI;CAKjB;CAEA,mBACE,OACA,QAAkB;EAAC;EAAa;EAAQ;EAAS;CAAK,GAC9C;EACR,IAAI;EAEJ,IAAI,SAAS,KAAK,GAChB,OAAO;OACF,IAAI,SAAS,KAAK,GACvB,OAAO,MAAM,SAAS;OACjB,IAAI,MAAM,QAAQ,KAAK,GAC5B,OAAO,KAAK,mBAAsB,MAAM,IAAI,KAAK;OAEjD,OAAO,KAAK,eAAe,OAAO,KAAK;EAGzC,OAAO,gBAAgB,IAAI;CAC7B;CAEA,kBAA0B,OAAgB;EACxC,IAAI,OAAiB,CAAC;EAEtB,IAAI,MAAM,QAAQ,KAAK,GACrB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,YAAY,KAAK,mBAAmB,IAAI;GAE9C,IAAI,WACF,KAAK,KAAK,SAAS;EAEvB;OACK,IAAI,SAAS,KAAK,GACvB,OAAO,YAAY,KAAK,mBAAmB,KAAK,GAAG,GAAG;EAGxD,OAAO,IAAI,IAAI,IAAI;CACrB;CAEA,WACE,MACA,YACU;EACV,IAAI,YAAe,MAAM,UAAU,GACjC,OAAO;EAGT,IAAI,YAAY,IAAI;QACb,MAAM,aAAa,KAAK,WAC3B,IAAI,YAAe,WAAW,UAAU,GACtC,OAAO;EAAA;EAKb,OAAO;CACT;CAEA,WAAmB,OAA+B;EAChD,IAAI,CAAC,cAAc,KAAK,GACtB,OAAO;EAGT,IAAI,SAAS,MAAM,MAAM,GACvB,OAAO,MAAM;EAGf,IAAI,SAAS,MAAM,GAAG,GACpB,OAAO,MAAM;EAGf,OAAO;CACT;CAEA,oBAA4B;EAC1B,KAAK,MAAM,QAAQ,KAAK,YACtB,IAAI,YAAY,IAAI,GAClB,KAAK,MAAM,aAAa,KAAK,WAC3B,KAAK,kBAAkB,SAAS;OAGlC,KAAK,kBAAkB,IAAI;CAGjC;CAEA,kBAA0B,KAAY;EACpC,IAAI,SAAS,GAAG,GACd,OAAO,KAAK,cAAc,GAAG;EAG/B,OAAO,KAAK,sBAAsB,GAAG;CACvC;CAEA,cAAsB,KAAmB;EACvC,KAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG;EAAI;EACvC,KAAK,kBAAkB;CACzB;CAEA,gCAAwC,OAAoC;EAC1E,IAAI,KAAK,kBAAkB,WAAW,GACpC,OAAO;EAIT,IAAI,UAAU,YACZ,OAAO;EAGT,OAAO,CAAC,KAAK;CACf;CAEA,sBAA8B,KAAY;EAExC,IAAI,UAAU,GAAG,GACf,KAAK,cAAc,KAAK,mBAAmB,GAAG;EAGhD,IAAI,UAAU,GAAG,KAAK,WAAW,IAAI,UAAU,GAC7C,KAAK,kBAAkB,IAAI,UAAU;EAIvC,IAAI,SAAS,GAAG,GAAG;GACjB,MAAM,MAAM,KAAK,WAAW,GAAG;GAC/B,IAAI,KACF,KAAK,OAAO,OAAO;EAEvB;EAGA,IAAI,kBAAkB,GAAG,GAAG;GAC1B,MAAM,MAAM,IAAI;GAChB,IAAI,KACF,KAAK,YAAY,OAAO;EAE5B;CACF;CAEA,mBAA2B,KAAwC;EACjE,MAAM,QAAQ,KAAK,OAAO;EAE1B,IAAI,CAAC,OAAO,OAAO;EAEnB,IAAI,SAAS,KAAK,GAAG;GACnB,KAAK,OAAO,KAAK,mBAAmB,IAAI,iBAAiB,OAAO;GAChE,OAAO;EACT;EAEA,IAAI,SAAS,KAAK,GAChB,OAAO,aAAa,KAAK;EAI3B,IAAI,WAAW,KAAK,KAAK,cAAc,OAErC,OAAO,aADU,KAAK,mBAAmB,MAAM,QACpB,CAAC;EAG9B,OAAO;CACT;CAEA,kBAA0B,OAA8C;EACtE,IAAI,SAAS,KAAK,GAEhB,OAAO,CAAC,uBAAuB,MADjB,kBAAkB,KACS,CAAC,CAAC,IAAI,qBAAqB,CAAC,CAAC;EAGxE,MAAM,eAA0B,MAAM,QAAQ,KAAK,IAC/C,MAAM,KAAK,IACX,CAAC,KAAK,CAAC,CAAC,KAAK;EAEjB,MAAM,SAAuC,CAAC;EAE9C,IAAI,eAAyD;GAC3D,MAAM;GACN,OAAO,CAAC;EACV;EAEA,KAAK,MAAM,QAAQ,cAAc;GAC/B,MAAM,OAAO,KAAK,mBAAmB,MAAM,CAAC,MAAM,CAAC;GACnD,MAAM,OAAO,KAAK,mBAAmB,MAAM,CAAC,MAAM,CAAC;GAEnD,IAAI,SAAS,IAAI,GACf,aAAa,MAAM,KAAK,gBAAgB,IAAI,CAAC;QACxC,IAAI,YAAY,IAAI,GAAG;IAC5B,IAAI,QAAQ,QAAQ,CAAC,KAAK,WAAW,KAAK,QAAQ,OAAO,EAAE,CAAC,GAC1D,aAAa,MAAM,KAAK,IAAI;IAG9B,IAAI,MACF,aAAa,MAAM,KAAK,IAAI;GAEhC,OAAO,IAAI,eAAe,IAAI,GAAG;IAE/B,IAAI,aAAa,MAAM,SAAS,GAC9B,OAAO,KACL,uBACE,aAAa,MACb,aAAa,MAAM,OAAO,OAAO,CAAC,CAAC,IAAI,qBAAqB,CAC9D,CACF;IAIF,eAAe;KAAE,MAAM,QAAQ;KAAM,OAAO,CAAC;IAAE;IAE/C,IAAI,KAAK,iBAAiB;KACxB,MAAM,eAAe,KAAK,kBAAkB,KAAK,eAAe;KAEhE,KAAK,MAAM,eAAe,cACxB,aAAa,MAAM,KAAK,GAAG,YAAY,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC;IAEpE;GACF,OAAO,IAAI,MACT,aAAa,MAAM,KAAK,IAAI;EAEhC;EAGA,IAAI,aAAa,MAAM,SAAS,GAC9B,OAAO,KACL,uBACE,aAAa,MACb,aAAa,MAAM,OAAO,OAAO,CAAC,CAAC,IAAI,qBAAqB,CAC9D,CACF;EAGF,OAAO;CACT;;;;CAMA,WAA6C;EAC3C,IAAI,eAAe,KAAK,OAAO,SAAS,GAAG;GACzC,MAAM,gBAAgB,KAAK,mBAAmB,KAAK,OAAO,WAAW,CACnE,QACA,eACF,CAAC;GAED,IAAI,eACF,OAAO;EAEX;EAEA,IAAI,CAAC,KAAK,aACR,MAAM,IAAI,mBAAmB,UAAU;EAGzC,OAAO,KAAK;CACd;CAEA,WAA4C;EAC1C,MAAM,WAAW,KAAK,mBAAmB,KAAK,OAAO,UAAU;EAE/D,IAAI,CAAC,UACH,MAAM,IAAI,mBAAmB,UAAU;EAGzC,OAAO;CACT;CAEA,QAAsC;EACpC,MAAM,QAAQ,KAAK,mBAAmB,KAAK,OAAO,IAAI;EAEtD,IAAI,CAAC,OACH,MAAM,IAAI,mBAAmB,OAAO;EAGtC,OAAO;CACT;CAEA,SAAwC;EACtC,IAAI,SAAS,KAAK,OAAO;EAEzB,IAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,GAC3C,SAAS,OAAO;EAGlB,MAAM,MAAM,KAAK,WAAW,MAAM;EAElC,IAAI,OAAO,KAAK,OAAO,MACrB,SAAS,KAAK,OAAO;EAGvB,MAAM,aAAa,KAAK,mBAAmB,QAAQ,CAAC,MAAM,CAAC;EAE3D,IAAI,CAAC,YACH,MAAM,IAAI,mBAAmB,QAAQ;EAGvC,OAAO;CACT;CAEA,cAAkD;EAChD,MAAM,OAAO,KAAK,mBAAmB,KAAK,OAAO,WAAW;EAE5D,IAAI,CAAC,MACH,MAAM,IAAI,mBAAmB,aAAa;EAG5C,OAAO;CACT;CAEA,QAAsC;EACpC,MAAM,QAAQ,KAAK,mBAAmB,KAAK,OAAO,OAAO,CACvD,OACA,YACF,CAAC;EAED,IAAI,CAAC,MAAM,WAAW,MAAM,GAC1B,MAAM,IAAI,mBAAmB,SAAS,KAAK;EAG7C,OAAO;CACT;CAEA,cAAkD;EAChD,MAAM,cACJ,KAAK,OAAO,oBAAoB,KAAK,OAAO,eAAe,CAAC;EAE9D,IAAI,CAAC,MAAM,QAAQ,WAAW,GAC5B,MAAM,IAAI,mBAAmB,eAAe,WAAW;EAGzD,MAAM,kBAAkB,YAAY,KAAK;EAEzC,MAAM,oCAAoB,IAAI,IAAY;EAE1C,KAAK,MAAM,QAAQ,iBAAiB;GAClC,MAAM,aAAa,KAAK,mBAAmB,IAAI;GAE/C,IAAI,YACF,kBAAkB,IAAI,UAAU;EAEpC;EAEA,IAAI,kBAAkB,SAAS,GAC7B,MAAM,IAAI,mBAAmB,eAAe,WAAW;EAGzD,OAAO,iBAAiB,KAAK,GAAG,CAAC,GAAG,iBAAiB,CAAC;CACxD;CAEA,eAAoD;EAClD,MAAM,eAAe,KAAK,kBAAkB,KAAK,OAAO,kBAAkB;EAE1E,IAAI,aAAa,WAAW,GAC1B,MAAM,IAAI,mBAAmB,cAAc;EAG7C,OAAO;CACT;CAEA,WAA4C;EAC1C,MAAM,WAAW,KAAK,OAAO;EAE7B,IAAI,CAAC,UACH,MAAM,IAAI,mBAAmB,UAAU;EAGzC,OAAO,KAAK,kBAAkB,QAAQ;CACxC;CAEA,SAAwC;EACtC,MAAM,SAAS,KAAK,mBAClB,KAAK,OAAO,eAAe,KAAK,OAAO,KACzC;EAEA,IAAI,CAAC,QACH,MAAM,IAAI,mBAAmB,UAAU,MAAM;EAG/C,OAAO,YAAY,MAAM;CAC3B;CAEA,YAA8C;EAC5C,MAAM,YAAY,KAAK,mBAAmB,WAAW;EAErD,IAAI,WAAW,OAAO;EAEtB,MAAM,WAAW,KAAK,mBAAmB,UAAU,KAAK;EACxD,MAAM,WAAW,KAAK,mBAAmB,UAAU,KAAK;EAExD,IAAI,YAAY,UACd,OAAO,WAAW;EAGpB,MAAM,IAAI,mBAAmB,WAAW;CAC1C;CAEA,WAA4C;EAC1C,OAAO,KAAK,mBAAmB,UAAU;CAC3C;CAEA,WAA4C;EAC1C,OAAO,KAAK,mBAAmB,UAAU;CAC3C;CAEA,UAA0C;EACxC,MAAM,UAAU,KAAK,OAAO;EAE5B,IAAI,CAAC,SACH,MAAM,IAAI,mBAAmB,SAAS;EAGxC,OAAO,KAAK,kBAAkB,OAAO;CACvC;CAEA,gBAAsD;EACpD,MAAM,gBAAgB,KAAK,mBAAmB,KAAK,OAAO,aAAa;EAEvE,IAAI,CAAC,eACH,MAAM,IAAI,mBAAmB,eAAe;EAG9C,OAAO;CACT;CAEA,UAA0C;EACxC,IAAI,UACF,KAAK,OAAO,mBACZ,KAAK,WAAW,KAAK,QAAQ,iBAAiB;EAEhD,IAAI;EAEJ,IAAI,kBAAkB,OAAO,GAAG;GAC9B,MAAM,WAAW,QAAQ;GAEzB,IAAI,YAAY,KAAK,YAAY,WAC/B,UAAU,KAAK,YAAY;GAG7B,cAAc,KAAK,mBAAmB,QAAQ,WAAW;EAC3D;EAEA,IAAI,CAAC,aACH,MAAM,IAAI,mBAAmB,SAAS;EAGxC,IAAI,QAAQ,OAAO,WAAW,WAAW;EAEzC,IAAI,kBAAkB,OAAO,KAAK,QAAQ,GAAG;GAC3C,MAAM,aAAa,OAAO,WACxB,KAAK,mBAAmB,QAAQ,UAAU,CAC5C;GACA,MAAM,cAAc,OAAO,WACzB,KAAK,mBAAmB,QAAQ,WAAW,CAC7C;GAEA,IAAI,CAAC,OAAO,MAAM,UAAU,KAAK,aAAa,GAAG;IAC/C,MAAM,aAAa,OAAO,MAAM,WAAW,IAAI,IAAI;IACnD,MAAM,QAAQ,aAAa;IAE3B,IAAI,QAAQ,GACV,SAAU,QAAQ,cAAc,QAAS;GAE7C;EACF;EAEA,OAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;CACnC;CAEA,eAAoD;EAClD,IAAI,UACF,KAAK,OAAO,mBACZ,KAAK,WAAW,KAAK,QAAQ,iBAAiB;EAEhD,IAAI;EAEJ,IAAI,kBAAkB,OAAO,GAAG;GAC9B,MAAM,WAAW,QAAQ;GAEzB,IAAI,YAAY,KAAK,YAAY,WAC/B,UAAU,KAAK,YAAY;GAG7B,eACE,KAAK,mBAAmB,QAAQ,WAAW,KAC3C,KAAK,mBAAmB,QAAQ,WAAW;EAC/C;EAEA,IAAI,CAAC,cACH,MAAM,IAAI,mBAAmB,cAAc;EAG7C,MAAM,QAAQ,OAAO,WAAW,YAAY;EAC5C,OAAO,UAAU,IAAI,KAAK,MAAM,KAAK,IAAI;CAC3C;CAEA,YAA8C;EAC5C,MAAM,YAAY,KAAK,OAAO;EAE9B,IAAI,CAAC,cAAc,SAAS,GAC1B,MAAM,IAAI,mBAAmB,aAAa,SAAS;EAGrD,MAAM,mCAAmB,IAAI,IAAoB;EAEjD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,GAAG;GACpD,IAAI,CAAC,OAAO,IAAI,WAAW,GAAG,KAAK,CAAC,OAAO;GAC3C,iBAAiB,IAAI,KAAK,KAAK,mBAAmB,KAAK,CAAC;EAC1D;EAEA,OAAO;CACT;CAEA,WAA4C;EAC1C,MAAM,WAAW,KAAK,OAAO;EAE7B,IAAI,CAAC,UACH,MAAM,IAAI,mBAAmB,UAAU;EAGzC,OAAO,KAAK,kBAAkB,QAAQ;CACxC;CAEA,sBAAkE;EAChE,MAAM,sBAAsB,KAAK,OAAO;EAExC,IAAI,CAAC,qBACH,MAAM,IAAI,mBAAmB,qBAAqB;EAGpD,MAAM,kCAAkB,IAAI,IAAY;EACxC,MAAM,OAAO,KAAK,kBAAkB,mBAAmB;EAEvD,KAAK,MAAM,QAAQ,MAAM;GACvB,MAAM,QAAQ,KAAK,QAAQ,6BAA6B,EAAE;GAC1D,IAAI,OACF,gBAAgB,IAAI,KAAK;EAE7B;EAEA,OAAO;CACT;AACF;;;AC9tBA,MAAM,uCAAuC;CAC3C,UAAU;CACV,0BAAU,IAAI,IAAY;CAC1B,UAAU;CACV,UAAU;CACV,WAAW;CACX,yBAAS,IAAI,IAAY;CACzB,eAAe;CACf,SAAS;CACT,cAAc;CACd,2BAAW,IAAI,IAAY;CAC3B,yBAAS,IAAI,IAAoB;CACjC,2BAAW,IAAI,IAAoB;CACnC,qCAAqB,IAAI,IAAY;CACrC,0BAAU,IAAI,IAAY;CAC1B,OAAO,KAAA;AACT;AAOA,SAAgB,sBACd,OACyC;CACzC,OAAO,SAAS;AAClB;AAEA,SAAgB,8BAEd,OAAmD;CACnD,MAAM,QAAQ,qCAAqC;CAEnD,IAAI,iBAAiB,KACnB,OAAO,IAAI,IAAI,KAAK;CAGtB,IAAI,iBAAiB,KACnB,OAAO,IAAI,IAAI,KAAK;CAGtB,OAAO;AACT;;;ACnCA,IAAa,kBAAb,MAAa,gBAAgB;CAIjB;CACS;CACA;CALnB;CAEA,YACE,SACA,aACA,UAAoD,CAAC,GACrD;EAHQ,KAAA,UAAA;EACS,KAAA,cAAA;EACA,KAAA,UAAA;EAEjB,KAAK,SAAS,IAAI,OAAO,KAAK,WAAW,GAAG,KAAK,QAAQ,QAAQ;EAGjE,KAAK,QAAQ,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CACrD;CAEA,WAAmB,SAAkB;EACnC,OAAO,GAAG,KAAK,YAAY,GAAG,gBAAgB,OAC5C,UAAU,IAAI,YAAY;CAE9B;CAEA,MAAM,QACJ,OACA,WAG4B;EAC5B,IAAI;EAEJ,KAAK,OAAO,MAAM,qBAAqB,OAAO;EAG9C,KAAK,MAAM,UAAU,KAAK,SAAS;GACjC,MAAM,eAAe,IAAI,OACvB,KAAK,WAAW,OAAO,IAAI,GAC3B,KAAK,QAAQ,QACf;GAOA,IANoB,OAAO,SAAS,KAMtB,KAAK,CAAC,UAAU,MAAM,GAClC,IAAI;IACF,SAAS,MAAM,OAAO,QAAQ,KAAK;GACrC,SAAS,KAAK;IACZ,IAAI,eAAe,2BACjB,aAAa,QAAQ,IAAI,OAAO;SAEhC,MAAM,IAAI,2BACR,OACA,WAAW,OAAO,KAAK,IACvB,GACF;GAEJ;QAEA,aAAa,QAAQ,2BAA2B,OAAO;EAE3D;EAGA,IAAI,WAAW;GACb,KAAK,OAAO,MAAM,sCAAsC,OAAO;GAC/D,KAAK,OAAO,QAAQ,oBAAoB,MAAM;GAE9C,IAAI;IACF,SAAS,MAAM,UAAU,MAAM;IAC/B,KAAK,OAAO,QAAQ,mBAAmB,MAAM,KAAK,MAAM;GAC1D,SAAS,KAAK;IACZ,IAAI,eAAe,2BACjB,KAAK,OAAO,QAAQ,IAAI,OAAO;SAE/B,MAAM,IAAI,2BACR,OACA,2BACA,GACF;GAEJ;EACF;EAGA,IAAI,CAAC,UAAU,sBAAsB,KAAK,GAAG;GAC3C,KAAK,OAAO,MAAM,4BAA4B,OAAO;GACrD,SAAS,8BAA8B,KAAK;EAC9C;EAEA,IAAI,UAAU,MAAM,GAClB,OAAO;EAGT,MAAM,IAAI,2BAA2B,KAAK;CAC5C;AACF;;;AC5DA,MAAM,2BAA2B,UAAyC;CACxE,OAAO,SAAS,KAAK,KAAK,SAAS,KAAK,KAAK,OAAO,UAAU;AAChE;AAEA,MAAM,+BACJ,UAC0C;CAC1C,OACE,cAAc,KAAK,KAAK,SAAS,SAAS,wBAAwB,MAAM,GAAG;AAE/E;AAEA,MAAM,sBACJ,SACG;CACH,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,MAAM,iBAAgC,CAAC;CAEvC,KAAK,MAAM,QAAQ,MACjB,IAAI,wBAAwB,IAAI,GAC9B,eAAe,KAAK,IAAI;MACnB,IAAI,4BAA4B,IAAI,GACzC,eAAe,KAAK,KAAK,GAAG;CAIhC,OAAO,eAAe,SAAS,IAAI,iBAAiB,KAAA;AACtD;AAEA,MAAM,uBACJ,QACA,OACA,YAK2B;CAC3B,OAAO;EACL,SAAS;EACT,OAAO;GACL,MAAM,SAAS,QAAQ;GACvB,MAAM,SAAS,QAAQ;GACvB,QAAQ,OAAO,KAAK,WAAW;IAC7B,SAAS,MAAM;IACf,MAAM,mBAAmB,MAAM,IAAI;IACnC,SAAS,WAAW,KAAK;GAC3B,EAAE;GACF;GACA,SAAS,SAAS;EACpB;CACF;AACF;AAEA,MAAM,mBACJ,WACgD;CAChD,OAAO,CAAC,OAAO;AACjB;;;;AAKA,SAAgB,mBACd,OAC4C;CAC5C,IAAI,CAAC,aAAa,KAAK,KAAK,EAAE,eAAe,QAC3C,OAAO;CAGT,MAAM,WAAW,MAAM;CAEvB,OACE,cAAc,QAAQ,KACtB,SAAS,YAAY,KACrB,SAAS,SAAS,MAAM,KACxB,WAAW,SAAS,QAAQ;AAEhC;;;;AAKA,eAAsB,4BACpB,QACA,OAC6B;CAC7B,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,YAAY,CAAC,SAAS,KAAK;EAEvD,IAAI,gBAAgB,MAAM,GACxB,OAAO;GAAE,SAAS;GAAM,MAAM,OAAO;EAAM;EAG7C,OAAO,oBAAoB,OAAO,MAAM;CAC1C,SAAS,OAAO;EAId,OAAO,oBAAoB,CAAC,EAAE,SAF5B,iBAAiB,QAAQ,MAAM,UAAU,2BAEL,CAAC,GAAG,KAAK;CACjD;AACF;;;;;;AChJA,SAAgB,eAAe,OAAyB;CACtD,OAAO,EAAE,MAAM;AACjB;;;;AAKA,SAAgB,gBACd,MACA,QAAoB,CAAC,GACV;CACX,OAAO;EAAE;EAAM;CAAM;AACvB;;;;AAgCA,SAAgB,eACd,QACA,YAA2B,MACpB;CAEP,OAAO,CAAC,gBAAgB,WADV,OAAO,IAAI,cACc,CAAC,CAAC;AAC3C;;;ACpDA,SAAS,gBAAgB,GAAe,UAAqC;CAC3E,IAAI,CAAC,UACH;CAQF,OALa,gBACX,EAAE,QAAQ,CAAC,CACR,KAAK,CAAC,CACN,QAAQ,WAAW,GAAG,CAEjB,KAAK,KAAA;AACjB;;;;;AAMA,SAAgB,iBAAiB,GAAe;CAG9C,MAAM,cAFkB,EAAE,wBAAwB,CAAC,CAAC,MAElB,CAAC,CAChC,KAAK,8BAA8B,CAAC,CACpC,MAAM;CAET,MAAM,iBACJ,YAAY,SAAS,IACjB,cACA,EAAE,8BAA8B,CAAC,CAAC,MAAM;CAE9C,IAAI,eAAe,WAAW,GAC5B;CAGF,MAAM,YAAY,eAAe,KAAK,oBAAoB,CAAC,CAAC,MAAM;CAElE,IAAI,UAAU,WAAW,GACvB;CAGF,MAAM,iBAAiB,UACpB,KAAK,IAAI,CAAC,CACV,KAAK,GAAG,OAAO,gBAAgB,GAAG,EAAE,CAAC,CAAC,CACtC,IAAI,CAAC,CACL,OAAO,OAAO;CAEjB,IAAI,eAAe,SAAS,GAC1B,OAAO,eAAe,cAAc;CAGtC,MAAM,aAAa,UAChB,SAAS,CAAC,CACV,QAAQ,CAAC,CACT,SAAS,SAAS;EACjB,IAAI,KAAK,SAAS,OAChB,OAAO,CAAC;EAKV,IAFc,EAAE,IAER,CAAC,CAAC,GAAG,cAAc,GACzB,OAAO,CAAC;EAGV,MAAM,OAAO,gBAAgB,GAAG,IAAI;EACpC,OAAO,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;CAEH,OAAO,WAAW,SAAS,IAAI,eAAe,UAAU,IAAI,KAAA;AAC9D;;;AC9BA,IAAsB,kBAAtB,MAAsC;CAWf;CACA;CACA;CAZrB;CACA;CACA;CACA,mBACE;CAEF;CACA,aAAuC;CAEvC,YACE,MACA,KACA,UAA6C,CAAC,GAC9C;EAHmB,KAAA,OAAA;EACA,KAAA,MAAA;EACA,KAAA,UAAA;EAEnB,MAAM,EACJ,kBAAkB,CAAC,GACnB,sBAAsB,CAAC,GACvB,WAAA,GACA,mBAAmB,UACjB;EAEJ,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,MAAM,QAAQ;EACxD,KAAK,IAAI,QAAQ,KAAK,IAAI;EAE1B,MAAM,iBAAoC,CACxC,IAAI,gBAAgB,KAAK,CAAC,GAC1B,IAAI,gBAAgB,KAAK,GAAG,QAAQ,CACtC;EAEA,MAAM,qBAA4C,CAAC,IAAI,mBAAmB,CAAC;EAG3E,IAAI,kBAAkB;GACpB,MAAM,gBAAwC,cAC5C,gBACF,IACI,mBACA,CAAC;GACL,mBAAmB,KAAK,IAAI,uBAAuB,aAAa,CAAC;EACnE;EAEA,KAAK,gBAAgB,IAAI,cACvB,gBACA,oBACA,iBACA,mBACF;EAEA,KAAK,kBAAkB,IAAI,gBACzB,KAAK,cAAc,cAAc,GACjC,KAAK,YAAY,MACjB,EAAE,SAAS,CACb;CACF;;;;;;CAOA,aAAmD,CAAC;;;;;CAMpD,MAAa,QACX,OAC4B;EAE5B,IAAI,QAAQ,MAAM,KAAK,gBAAgB,QACrC,OACA,KAAK,WAAW,MAClB;EAGA,KAAK,MAAM,aAAa,KAAK,cAAc,kBAAkB,GAC3D,QAAQ,MAAM,UAAU,QAAQ,OAAO,KAAK;EAG9C,OAAO;CACT;;;;;CAMA,OAAO,OAAe;EACpB,MAAM,IAAI,wBAAwB,MAAM;CAC1C;;;;;CAMA,UAA4B;EAE1B,OADiB,KAAK,YACN,KAAK;CACvB;;;;;CAOA,eAA6C;EAC3C,MAAM,gBAAgB,KAAK,EAAE,yBAAuB,CAAC,CAAC,KAAK,MAAM;EAEjE,MAAM,OAAO,IAAI,IACf,KAAK,IAAI,WAAW,MAAM,IAAI,KAAK,MAAM,WAAW,KAAK,KAC3D;EAEA,OAAO,gBAAgB,IAAI,IAAI,eAAe,IAAI,CAAC,CAAC,OAAO,KAAK;CAClE;CAEA,WAAqC;EACnC,MAAM,WAAW,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM;EAE3C,IAAI,UACF,OAAO;EAKT,MAAM,WAAW,KAAK,EAAE,uCAAqC,CAAC,CAAC,KAC7D,SACF;EAEA,IAAI,UACF,OAAO,SAAS,MAAM,GAAG,CAAC,CAAC;EAG7B,KAAK,OAAO,KAAK,8BAA8B;EAE/C,OAAO;CACT;CAEA,QAA+B;EAC7B,IAAI,CAAC,KAAK,QAAQ,cAAc,OAAO,KAAA;EAEvC,OAAO,KAAK,EAAE,SAAS,CAAC,CACrB,KAAK,GAAG,OAAO;GACd,MAAM,OAAO,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM;GACnC,IAAI,CAAC,MAAM,WAAW,MAAM,GAAG,OAAO;GACtC,OAAO;IAAE;IAAM,MAAM,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK;GAAE;EAChD,CAAC,CAAC,CACD,IAAI,CAAC,CACL,OAAO,OAAO;CACnB;CAEA,QAAuC;EACrC,OAAO,iBAAiB,KAAK,CAAC;CAChC;CAEA,MAAc,gBAAiD;EAC7D,IAAI;GACF,OAAO,MAAM,KAAK,QAAQ,QAAQ;EACpC,SAAS,OAAO;GACd,MAAM,gBAAgB,KAAK,QAAQ,eAAe,KAAK;GAEvD,IAAI,iBAAiB,8BAA8B,eACjD,OAAO;GAGT,MAAM;EACR;CACF;;;;CAKA,MAAa,SAA8B;EACzC,IAAI,KAAK,YACP,OAAO,KAAK;EAGd,MAAM,QAAQ,KAAK,QAAQ,aAAa,KAAK,MAAM,IAAI,KAAA;EAEvD,KAAK,aAAa;GAChB,QAAQ,MAAM,KAAK,QAAQ,QAAQ;GACnC,cAAc,KAAK,aAAa;GAChC,UAAU,MAAM,KAAK,QAAQ,UAAU;GACvC,UAAU,MAAM,KAAK,QAAQ,UAAU;GACvC,eAAe,MAAM,KAAK,QAAQ,eAAe;GACjD,SAAS,MAAM,KAAK,QAAQ,SAAS;GACrC,aAAa,MAAM,KAAK,QAAQ,aAAa;GAC7C,qBAAqB,MAAM,KAAK,QAAQ,qBAAqB;GAC7D,WAAW,MAAM,KAAK,QAAQ,WAAW;GACzC,MAAM,KAAK,QAAQ;GACnB,OAAO,MAAM,KAAK,QAAQ,OAAO;GACjC,aAAa,MAAM,KAAK,QAAQ,aAAa;GAC7C,cAAc,MAAM,KAAK,QAAQ,cAAc;GAC/C,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB,UAAU,MAAM,KAAK,QAAQ,UAAU;GACvC,UAAU,KAAK,SAAS;GACxB,OAAO,KAAK,MAAM;GAClB,WAAW,MAAM,KAAK,QAAQ,WAAW;GACzC,UAAU,MAAM,KAAK,QAAQ,UAAU;GACvC,SAAS,MAAM,KAAK,QAAQ,SAAS;GACrC,cAAc,MAAM,KAAK,QAAQ,cAAc;GAC/C,SAAS,MAAM,KAAK,QAAQ,SAAS;GACrC,UAAU,MAAM,KAAK,QAAQ,UAAU;GACvC,OAAO,MAAM,KAAK,QAAQ,OAAO;GACjC,WAAW,MAAM,KAAK,QAAQ,WAAW;GACzC,QAAQ,MAAM,KAAK,cAAc;EACnC;EAEA,OAAO,KAAK;CACd;;;;;CAMA,MAAa,iBAA+D;EAC1E,MAAM,EACJ,UACA,SACA,qBACA,WACA,UACA,WACA,SACA,GAAG,SACD,MAAM,KAAK,OAAO;EAEtB,OAAO;GACL,GAAG;GACH,UAAU,MAAM,KAAK,QAAQ;GAC7B,SAAS,MAAM,KAAK,OAAO;GAC3B,qBAAqB,MAAM,KAAK,mBAAmB;GACnD,WAAW,MAAM,KAAK,SAAS;GAC/B,UAAU,MAAM,KAAK,QAAQ;GAC7B,WAAW,OAAO,YAAY,SAAS;GACvC,SAAS,OAAO,YAAY,OAAO;EACrC;CACF;;;;;;CAOA,YAAsB;EACpB,OAAO;CACT;;;;;;;;CASA,sBAAyE;EACvE,IAAI,KAAK,kBACP,OAAO,KAAK;EAGd,MAAM,SAAS,KAAK,QAAQ,UAAU,KAAK,UAAU;EAErD,IAAI,CAAC,mBAAiC,MAAM,GAC1C,MAAM,IAAI,MACR,0DACF;EAGF,KAAK,mBAAmB;EACxB,OAAO,KAAK;CACd;;;;;;;;CASA,MAAM,QAA+B;EACnC,MAAM,MAAM,MAAM,KAAK,eAAe;EACtC,MAAM,SAAS,MAAM,4BACnB,KAAK,oBAAoB,GACzB,GACF;EAEA,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,oBAAoB,OAAO,MAAM,QAAQ,OAAO,MAAM,KAAK;EAGvE,OAAO,OAAO;CAChB;;;;;;;CAQA,MAAM,YAAoD;EACxD,IAAI;GACF,MAAM,MAAM,MAAM,KAAK,eAAe;GACtC,OAAO,4BAA4B,KAAK,oBAAoB,GAAG,GAAG;EACpE,SAAS,OAAO;GACd,IAAI,iBAAiB,4BACnB,OAAO;IACL,SAAS;IACT,OAAO;KACL,MAAM;KACN,MAAM;KACN,SAAS,EAAE,OAAO,MAAM,MAAM;KAC9B,QAAQ,CACN;MACE,SAAS,MAAM;MACf,MAAM,CAAC,MAAM,KAAK;MAClB,SAAS,MAAM;KACjB,CACF;KACA,OAAO;IACT;GACF;GAGF,IAAI,iBAAiB,4BACnB,OAAO;IACL,SAAS;IACT,OAAO;KACL,MAAM;KACN,MAAM;KACN,SAAS;MAAE,OAAO,MAAM;MAAO,QAAQ,MAAM;KAAO;KACpD,QAAQ,CACN;MACE,SAAS,MAAM;MACf,MAAM,CAAC,MAAM,KAAK;MAClB,SAAS,MAAM;KACjB,CACF;KACA,OAAO,MAAM,mBAAmB;IAClC;GACF;GAGF,IAAI,iBAAiB,2BACnB,OAAO;IACL,SAAS;IACT,OAAO;KACL,MAAM;KACN,MAAM;KACN,SAAS,EAAE,OAAO,MAAM,MAAM;KAC9B,QAAQ,CACN;MACE,SAAS,MAAM;MACf,MAAM,CAAC,MAAM,KAAK;MAClB,SAAS,MAAM;KACjB,CACF;KACA,OAAO;IACT;GACF;GAKF,OAAO;IACL,SAAS;IACT,OAAO;KACL,MAAM;KACN,MAAM;KACN,QAAQ,CAAC,EAAE,SAPC,oBAAoB,OAAO,0BAOtB,EAAE,CAAC;KACpB,OAAO;IACT;GACF;EACF;CACF;AACF;;;ACjZA,MAAM,6BAA6B,EAAE,OAAO,EAC1C,QAAQ,EAAE,OAAO;CACf,KAAK,EAAE,OAAO;CACd,SAAS,EAAE,OAAO;CAClB,UAAU,EAAE,OAAO;CACnB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,kBAAkB,EAAE,QAAQ;CAC5B,YAAY,EAAE,OAAO;EACnB,aAAa,EAAE,OAAO;EACtB,QAAQ,EAAE,OAAO;GACf,OAAO,EAAE,OAAO;GAChB,aAAa,EAAE,OAAO;GACtB,MAAM,EAAE,OAAO;EACjB,CAAC;CACH,CAAC;AACH,CAAC,EACH,CAAC;AAID,MAAM,8BAA8B,EAAE,OAAO,EAC3C,QAAQ,EAAE,OAAO;CACf,OAAO,EAAE,OAAO;CAChB,uBAAuB,EAAE,MAAM,0BAA0B;AAC3D,CAAC,EACH,CAAC;AAED,MAAM,0BAA0B,EAAE,OAAO,EACvC,QAAQ,EAAE,OAAO,EACf,SAAS,EAAE,OAAO,EACpB,CAAC,EACH,CAAC;AAED,MAAM,mBAAmB,EAAE,OAAO;CAChC,eAAe,EAAE,OAAO;CACxB,gBAAgB,EAAE,OAAO,CAAC,CAAC,SAAS;CACpC,kBAAkB,EAAE,MAAM,2BAA2B;CACrD,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,cAAc,EAAE,MAAM,uBAAuB;CAC7C,UAAU,EAAE,OAAO,EACjB,QAAQ,EAAE,OAAO,EACf,OAAO,EAAE,OAAO,EACd,KAAK,EAAE,IAAI,EACb,CAAC,EACH,CAAC,EACH,CAAC;AACH,CAAC;AAID,MAAM,sBAAsB,EAAE,OAAO,EACnC,OAAO,EAAE,OAAO,EACd,WAAW,EAAE,OAAO,EAClB,MAAM,iBACR,CAAC,EACH,CAAC,EACH,CAAC;AAED,IAAa,sBAAb,cAAyC,gBAAgB;CACvD,OAAkC;CAElC,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC;EACvC,OAAO,KAAK,MAAM,KAAK,IAAI;EAC3B,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,cAAc,KAAK,aAAa,KAAK,IAAI;EACzC,UAAU,KAAK,SAAS,KAAK,IAAI;CACnC;CAEA,WAA+C;EAC7C,OAAO;CACT;CAEA,MACE,WACuB;EACvB,MAAM,OAAO,KAAK,cAAc;EAEhC,IAAI,CAAC,MAAM;GACT,IAAI,WACF,OAAO;GAET,MAAM,IAAI,MAAM,yBAAyB;EAC3C;EAEA,OAAO,KAAK,SAAS,OAAO,MAAM;CACpC;CAEA,YACE,WAC6B;EAG7B,IAAI,cAAc,KAAK,iBAAiB;EAExC,IAAI,CAAC,aACH,cAAc,KAAK,qBAAqB,SAAS;EAGnD,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,+BAA+B;EAGjD,OAAO;CACT;CAEA,aACE,WAC8B;EAC9B,MAAM,OAAO,KAAK,cAAc;EAEhC,IAAI,CAAC,MAAM;GACT,IAAI,WACF,OAAO;GAET,MAAM,IAAI,MAAM,gCAAgC;EAClD;EAEA,MAAM,EAAE,aAAa;EAErB,MAAM,QAAkB,CAAC;EAEzB,IAAI,UACF,MAAM,KAAK,SAAS,gBAAgB,QAAQ,GAAG;EAGjD,KAAK,MAAM,eAAe,KAAK,cAC7B,MAAM,KAAK,gBAAgB,YAAY,OAAO,OAAO,CAAC;EAGxD,OAAO,CAAC,uBAAuB,MAAM,MAAM,IAAI,qBAAqB,CAAC,CAAC;CACxE;CAEA,qBACE,WACoC;EAEpC,MAAM,kBAAkB;EACxB,MAAM,qBAAqB;EAE3B,IAAI,aAAa,UAAU,SAAS,GAAG;GACrC,MAAM,SAAS,mBAAmB,SAAS;GAC3C,OAAO,iBACL,KAAK,GACL,QACA,iBACA,kBACF;EACF;EAEA,OAAO;CACT;CAEA,gBAA2C;EACzC,IAAI,KAAK,SAAS,MAAM;GAEtB,MAAM,aADc,KAAK,EAAE,mCACE,CAAC,CAAC,KAAK;GAEpC,IAAI,CAAC,YAAY;IACf,KAAK,OAAO,KAAK,qCAAqC;IACtD,OAAO;GACT;GAEA,IAAI;IACF,MAAM,SAAS,oBAAoB,MAAM,KAAK,MAAM,UAAU,CAAC;IAC/D,KAAK,OAAO,OAAO,MAAM,UAAU;GACrC,SAAS,OAAO;IACd,KAAK,OAAO,MAAM,8BAA8B,KAAK;IACrD,OAAO;GACT;EACF;EAEA,OAAO,KAAK;CACd;CAEA,oBAA4B,gBAA8C;EACxE,MAAM,EAAE,WAAW;EACnB,MAAM,YAAY;GAChB,OAAO,OAAO;GACd,OAAO,eAAe;GACtB,OAAO,WAAW,OAAO,SAAS;GAClC,OAAO,YAAY;EACrB;EAEA,MAAM,oBAA8B,CAAC;EAErC,KAAK,MAAM,YAAY,WACrB,IAAI,UACF,kBAAkB,KAAK,SAAS,QAAQ,CAAC;EAI7C,OAAO,kBAAkB,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,MAAM,GAAG;CAChE;CAEA,mBAA+D;EAC7D,MAAM,OAAO,KAAK,cAAc;EAEhC,IAAI,CAAC,MACH,OAAO;EAGT,MAAM,EAAE,qBAAqB;EAE7B,MAAM,SAAsB,CAAC;EAE7B,KAAK,MAAM,SAAS,kBAAkB;GACpC,MAAM,aACJ,MAAM,OAAO,MAAM,WAAW,IAAI,OAAO,MAAM,OAAO;GACxD,MAAM,QAAQ,MAAM,OAAO,sBAAsB,KAAK,SACpD,qBAAqB,KAAK,oBAAoB,IAAI,CAAC,CACrD;GAEA,OAAO,KAAK,sBAAsB,YAAY,KAAK,CAAC;EACtD;EAEA,OAAO;CACT;AACF;;;ACvOA,IAAa,cAAb,cAAiC,gBAAgB;CAC/C,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC,EACvC,aAAa,KAAK,YAAY,KAAK,IAAI,EACzC;CAEA,YACE,WAC6B;EAC7B,MAAM,kBAAkB;EACxB,MAAM,qBAAqB;EAE3B,IAAI,aAAa,UAAU,SAAS,GAAG;GACrC,MAAM,SAAS,mBAAmB,SAAS;GAE3C,OAAO,iBACL,KAAK,GACL,QACA,iBACA,kBACF;EACF;EAEA,MAAM,IAAI,4BAA4B;CACxC;AACF;;;ACrBA,IAAa,WAAb,cAA8B,gBAAgB;CAC5C,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC;EACvC,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,cAAc,KAAK,aAAa,KAAK,IAAI;CAC3C;CAEA,YACE,WAC6B;EAC7B,IAAI,aAAa,UAAU,SAAS,GAClC,OAAO;EAGT,MAAM,QAAQ,KAAK,EAAE,wBAAwB,CAAC,CAC3C,QAAQ,CAAC,CACT,KAAK,YAAY,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACzD,QAAQ,UAAU,MAAM,SAAS,CAAC,CAAC,CACnC,IAAI,oBAAoB;EAE3B,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,MAAM,+BAA+B;EAGjD,OAAO,CAAC,sBAAsB,MAAM,KAAK,CAAC;CAC5C;CAEA,aACE,WAC8B;EAC9B,IAAI,aAAa,UAAU,SAAS,GAClC,OAAO;EAGT,MAAM,QAAQ,KAAK,EAAE,oBAAoB,CAAC,CACvC,QAAQ,CAAC,CACT,KAAK,YAAY,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACzD,QAAQ,UAAU,MAAM,SAAS,CAAC,CAAC,CACnC,IAAI,qBAAqB;EAE5B,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,MAAM,gCAAgC;EAGlD,OAAO,CAAC,uBAAuB,MAAM,KAAK,CAAC;CAC7C;AACF;;;AC/CA,SAAS,mBAAmB,GAAyB,UAA0B;CAC7E,OAAO,gBAAgB,EAAE,QAAQ,CAAC,CAAC,KAAK,SAAS,CAAC;AACpD;AAEA,SAAS,gBAAgB,OAAuB;CAC9C,OAAO,gBAAgB,MAAM,QAAQ,oCAAoC,EAAE,CAAC;AAC9E;AAEA,IAAa,kBAAb,cAAqC,gBAAgB;CACnD,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC;EACvC,QAAQ,KAAK,OAAO,KAAK,IAAI;EAC7B,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,cAAc,KAAK,aAAa,KAAK,IAAI;EACzC,OAAO,KAAK,MAAM,KAAK,IAAI;EAC3B,QAAQ,KAAK,OAAO,KAAK,IAAI;CAC/B;CAEA,MACE,WACuB;EACvB,MAAM,UAAU,gBACd,KAAK,EAAE,qCAAmC,CAAC,CAAC,KAAK,CACnD;EACA,IAAI,SAAS,OAAO;EAEpB,MAAM,YAAY,mBAAmB,KAAK,GAAG,6BAA2B;EACxE,IAAI,WAAW,OAAO,gBAAgB,SAAS;EAE/C,IAAI,WAAW,OAAO,gBAAgB,SAAS;EAE/C,MAAM,IAAI,MAAM,yBAAyB;CAC3C;CAEA,OACE,WACwB;EACxB,IAAI,WAAW,OAAO;EAEtB,MAAM,WAAW,mBAAmB,KAAK,GAAG,iCAA+B;EAC3E,IAAI,UAAU,OAAO;EAErB,OAAO;CACT;CAEA,YACE,WAC6B;EAC7B,MAAM,gBAAgB,KAAK,oBAAoB;EAC/C,MAAM,uBAAuB,KAAK,qBAAqB;EACvD,MAAM,cACJ,wBAAwB,IACpB,cAAc,MAAM,GAAG,oBAAoB,IAC3C;EACN,MAAM,iBAAiB,KAAK,EAAE,WAAW,CAAC,CACvC,KAAK,GAAG,CAAC,CACT,QAAQ,CAAC,CACT,KAAK,YAAY,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACzD,MACE,UACC,SACA,CAAC,iBAAiB,KAAK,KAAK,KAC5B,CAAC,mBAAmB,KAAK,KAAK,KAC9B,CAAC,oBAAoB,KAAK,KAAK,KAC/B,mBAAmB,KAAK,MAAM,KAClC;EAEF,IAAI,gBAAgB,OAAO;EAE3B,MAAM,kBACJ,mBAAmB,KAAK,GAAG,gCAA8B,KACzD,mBAAmB,KAAK,GAAG,mCAAiC;EAC9D,IAAI,iBAAiB,OAAO;EAE5B,IAAI,WAAW,OAAO;EAEtB,MAAM,IAAI,MAAM,+BAA+B;CACjD;CAEA,sBAA8B;EAC5B,OAAO,KAAK,EAAE,sCAAsC,CAAC,CAAC,QAAQ;CAChE;CAEA,uBAAuC;EACrC,OAAO,KAAK,oBAAoB,CAAC,CAAC,WAAW,YAAY;GACvD,MAAM,OAAO,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC;GACnD,OAAO,iBAAiB,KAAK,IAAI;EACnC,CAAC;CACH;CAEA,cAAqD;EACnD,MAAM,gBAAgB,KAAK,oBAAoB;EAC/C,MAAM,uBAAuB,KAAK,qBAAqB;EAEvD,IAAI,uBAAuB,GACzB,MAAM,IAAI,4BAA4B;EAGxC,MAAM,QAAmD,CAAC;EAE1D,KAAK,MAAM,WAAW,cAAc,MAAM,oBAAoB,GAAG;GAC/D,MAAM,YAAY,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ;GAErD,IAAI,UAAU,SAAS,GAAG;IACxB,MAAM,KACJ,GAAG,UACA,KAAK,SAAS,mBAAmB,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACtD,QAAQ,UAAU,MAAM,SAAS,CAAC,CAAC,CACnC,IAAI,oBAAoB,CAC7B;IACA;GACF;GAEA,MAAM,aAAa,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,QAAQ;GAErD,KAAK,MAAM,aAAa,YAAY;IAClC,MAAM,OAAO,gBAAgB,KAAK,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC;IAErD,IAAI,oBAAoB,KAAK,IAAI,GAC/B,OAAO,CAAC,sBAAsB,MAAM,KAAK,CAAC;IAG5C,IACE,CAAC,QACD,iBAAiB,KAAK,IAAI,KAC1B,mBAAmB,KAAK,IAAI,GAE5B;IAGF,MAAM,aAAa,mBAAmB,IAAI;IAE1C,IAAI,eAAe,MACjB,MAAM,KAAK,qBAAqB,UAAU,CAAC;GAE/C;EACF;EAEA,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,4BAA4B;EAGxC,OAAO,CAAC,sBAAsB,MAAM,KAAK,CAAC;CAC5C;CAEA,eAAuD;EACrD,MAAM,gBAAgB,KAAK,oBAAoB;EAC/C,MAAM,uBAAuB,KAAK,qBAAqB;EAEvD,IAAI,uBAAuB,GACzB,OAAO,CAAC;EAGV,KAAK,MAAM,WAAW,cAAc,MAAM,uBAAuB,CAAC,GAAG;GACnE,MAAM,mBAAmB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,QAAQ;GAE/D,IAAI,iBAAiB,SAAS,GAC5B,OAAO,CACL,uBACE,MACA,iBACG,KAAK,SAAS,gBAAgB,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACnD,QAAQ,UAAU,MAAM,SAAS,CAAC,CAAC,CACnC,IAAI,qBAAqB,CAC9B,CACF;EAEJ;EAEA,MAAM,QAAkB,CAAC;EAEzB,KAAK,MAAM,WAAW,cAAc,MAAM,uBAAuB,CAAC,GAAG;GACnE,MAAM,OAAO,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC;GAEnD,IAAI,CAAC,QAAQ,QAAQ,KAAK,IAAI,KAAK,oBAAoB,KAAK,IAAI,GAC9D;GAGF,MAAM,KAAK,GAAG,0BAA0B,IAAI,CAAC;EAC/C;EAEA,OAAO,CACL,uBACE,MACA,MAAM,QAAQ,UAAU,MAAM,SAAS,CAAC,CAAC,CAAC,IAAI,qBAAqB,CACrE,CACF;CACF;CAEA,OACE,WACwB;EACxB,IAAI,WAAW,OAAO;EAMtB,MAAM,WAAW,gBAJA,gBAAgB,KAAK,EAAE,oBAAoB,CAAC,CAAC,KAAK,CAC9C,CAAC,CAAC,MACrB,mDAEmC,CAAC,GAAG,EAAE;EAE3C,IAAI,UACF,OAAO;EAGT,OAAO;CACT;AACF;;;AC7NA,IAAa,gBAAb,cAAmC,gBAAgB;CACjD,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC,EACvC,UAAU,KAAK,SAAS,KAAK,IAAI,EACnC;CAEA,SACE,YAC0B;EAC1B,OAAO;CACT;AACF;;;ACdA,IAAa,aAAb,cAAgC,gBAAgB;CAC9C,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC,EACvC,QAAQ,KAAK,OAAO,KAAK,IAAI,EAC/B;CAEA,SAA2C;EAEzC,OADe,KAAK,EAAE,wBAAsB,CAAC,CAAC,KAAK,CAAC,CAAC,KACzC;CACd;AACF;;;ACXA,IAAa,gBAAb,cAAmC,gBAAgB;CACjD,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC;EACvC,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,UAAU,KAAK,SAAS,KAAK,IAAI;CACnC;CAEA,YACE,WAC6B;EAC7B,MAAM,kBAAkB;EACxB,MAAM,qBAAqB;EAE3B,IAAI,aAAa,UAAU,SAAS,GAAG;GACrC,MAAM,SAAS,mBAAmB,SAAS;GAE3C,OAAO,iBACL,KAAK,GACL,QACA,iBACA,kBACF;EACF;EAEA,MAAM,IAAI,4BAA4B;CACxC;CAEA,SACE,YAC0B;EAC1B,OAAO;CACT;AACF;;;AC5BA,IAAa,UAAb,cAA6B,gBAAgB;CAC3C,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC;EACvC,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,cAAc,KAAK,aAAa,KAAK,IAAI;EACzC,UAAU,KAAK,SAAS,KAAK,IAAI;EACjC,WAAW,KAAK,UAAU,KAAK,IAAI;CACrC;CAEA,YACE,WAC6B;EAC7B,IAAI,aAAa,UAAU,SAAS,GAClC,OAAO;EAGT,MAAM,QAAQ,KAAK,EAAE,+CAA+C,CAAC,CAClE,QAAQ,CAAC,CACT,KAAK,YAAY,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACzD,QAAQ,UAAU,MAAM,SAAS,CAAC,CAAC,CACnC,IAAI,oBAAoB;EAE3B,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,MAAM,+BAA+B;EAGjD,OAAO,CAAC,sBAAsB,MAAM,KAAK,CAAC;CAC5C;CAEA,aACE,WAC8B;EAC9B,IAAI,aAAa,UAAU,SAAS,GAClC,OAAO;EAGT,MAAM,QAAQ,KAAK,EAAE,oCAAoC,CAAC,CACvD,QAAQ,CAAC,CACT,KAAK,YAAY,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACzD,QAAQ,UAAU,MAAM,SAAS,CAAC,CAAC,CACnC,IAAI,qBAAqB;EAE5B,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,MAAM,gCAAgC;EAGlD,OAAO,CAAC,uBAAuB,MAAM,KAAK,CAAC;CAC7C;CAEA,SACE,WAC0B;EAC1B,OAAO,KAAK,aACV,oEACA,SACF;CACF;CAEA,SACE,WAC0B;EAC1B,OAAO,KAAK,aACV,oEACA,SACF;CACF;CAEA,UACE,WAC2B;EAC3B,MAAM,WAAW,KAAK,aACpB,oEACA,IACF;EACA,MAAM,WAAW,KAAK,aACpB,oEACA,IACF;EAEA,IAAI,aAAa,QAAQ,aAAa,MACpC,QAAQ,YAAY,MAAM,YAAY;EAGxC,OAAO,aAAa;CACtB;CAEA,aACE,UACA,UACe;EACf,MAAM,QAAQ,gBAAgB,KAAK,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC;EAE7D,IAAI,CAAC,OACH,OAAO,YAAY;EAGrB,IAAI;GACF,OAAO,aAAa,KAAK;EAC3B,QAAQ;GACN,OAAO,YAAY;EACrB;CACF;AACF;;;AC7GA,MAAM,iBAAiB,EAAE,OAAO,EAC9B,OAAO,EAAE,OAAO,EACd,WAAW,EAAE,OAAO,EAClB,QAAQ,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,EACrC,CAAC,EACH,CAAC,EACH,CAAC,EACH,CAAC;AAMD,IAAa,UAAb,cAA6B,gBAAgB;CAC3C,iBAA4D,KAAA;CAE5D,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC,EACvC,aAAa,KAAK,YAAY,KAAK,IAAI,EACzC;CAEA,YACE,WAC6B;EAE7B,MAAM,kBAAkB;EACxB,MAAM,qBAAqB;EAE3B,IAAI,aAAa,UAAU,SAAS,GAAG;GACrC,MAAM,SAAS,mBAAmB,SAAS;GAE3C,OAAO,iBACL,KAAK,GACL,QACA,iBACA,kBACF;EACF;EAEA,MAAM,IAAI,4BAA4B;CACxC;CAEA,QAAgD;EAC9C,MAAM,eAAe,KAAK,aAAa;EAEvC,IAAI,cACF,OAAO;EAGT,OAAO,KAAK,SAAS;CACvB;CAEA,oBAAmD;EACjD,IAAI,KAAK,mBAAmB,KAAA,GAC1B,OAAO,KAAK;EAGd,MAAM,MAAM,KAAK,EAAE,gBAAgB,CAAC,CAAC,KAAK;EAE1C,IAAI,CAAC,KAAK;GACR,KAAK,OAAO,KAAK,8CAA8C;GAC/D,KAAK,iBAAiB;GACtB,OAAO,KAAK;EACd;EAEA,IAAI;GACF,MAAM,EAAE,SAAS,oBAAoB,GAAG;GACxC,MAAM,SAAS,eAAe,MAAM,IAAI;GACxC,KAAK,iBAAiB,OAAO,MAAM,UAAU;EAC/C,SAAS,OAAO;GACd,KAAK,OAAO,KAAK,0CAA0C,KAAK;GAChE,KAAK,iBAAiB;EACxB;EAEA,OAAO,KAAK;CACd;CAEA,eAA4C;EAE1C,MAAM,UADO,KAAK,kBAAkB,CAAC,EAAE,QAAQ,CAAC,EAAA,CAC5B,IAAI,eAAe,CAAC,CAAC,QAAQ,UAAU,MAAM,SAAS,CAAC;EAE3E,IAAI,OAAO,WAAW,GACpB;EAGF,OAAO,eAAe,MAAM;CAC9B;CAEA,WAAwC;EACtC,MAAM,YAAY,KAAK,EAAE,2BAAyB,CAAC,CAAC,MAAM;EAE1D,IAAI,UAAU,WAAW,GACvB;EAGF,MAAM,aAAa,UAChB,KAAK,OAAO,CAAC,CACb,QAAQ,CAAC,CACT,KAAK,YAAY,gBAAgB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACzD,QAAQ,UAAU,MAAM,SAAS,CAAC;EAErC,IAAI,WAAW,SAAS,GACtB,OAAO,eAAe,UAAU;EAGlC,MAAM,UAAU,UAAU,MAAM;EAChC,QAAQ,KAAK,gBAAgB,CAAC,CAAC,OAAO;EAEtC,MAAM,OAAO,gBAAgB,QAAQ,KAAK,CAAC;EAE3C,IAAI,CAAC,MACH;EAGF,OAAO,eAAe,CAAC,IAAI,CAAC;CAC9B;AACF;;;AC7HA,IAAa,gBAAb,cAAmC,gBAAgB;CACjD,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC,EACvC,QAAQ,KAAK,OAAO,KAAK,IAAI,EAC/B;CAEA,OACE,WACwB;EACxB,IAAI,aAAa,gBAAgB,SAAS,GACxC,OAAO;EAGT,MAAM,SAAS,gBACb,KAAK,EAAE,uBAAqB,CAAC,CAAC,KAAK,SAAS,KAC1C,KAAK,EAAE,8BAA4B,CAAC,CAAC,KAAK,SAAS,CACvD;EAEA,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,0BAA0B;EAG5C,OAAO;CACT;AACF;;;;;;;;AChBA,SAAS,mBAAmB,QAA4B;CAEtD,OAAO,OAAO,QAAQ,UAAU;EAC9B,MAAM,UAAU,MAAM,KAAK,CAAC,CAAC,YAAY;EACzC,OAAO,EAAE,QAAQ,WAAW,MAAM,KAAK,QAAQ,SAAS,GAAG;CAC7D,CAAC;AACH;AAEA,IAAa,gBAAb,cAAmC,gBAAgB;CACjD,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC;EACvC,aAAa,KAAK,YAAY,KAAK,IAAI;EACvC,cAAc,KAAK,aAAa,KAAK,IAAI;CAC3C;;;;;CAMA,YACE,WAC6B;EAC7B,MAAM,kBAAkB;EACxB,MAAM,qBAAqB;EAE3B,IAAI,aAAa,UAAU,SAAS,GAAG;GAGrC,MAAM,SAAS,mBADG,mBAAmB,SACK,CAAC;GAE3C,OAAO,iBACL,KAAK,GACL,QACA,iBACA,kBACF;EACF;EAEA,MAAM,IAAI,4BAA4B;CACxC;;;;;CAMA,eAAuD;EAErD,MAAM,QAAQ,KAAK,EAAE,qCAAqC,CAAC,CAAC,QAAQ;EAEpE,IAAI,MAAM,WAAW,GACnB,OAAO,CAAC;EAaV,OAAO,CAAC,uBAAuB,MAVjB,MACX,KAAK,OAAO;GAEX,MAAM,SAAS,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM;GAChC,OAAO,KAAK,sBAAsB,CAAC,CAAC,OAAO;GAC3C,OAAO,gBAAgB,OAAO,KAAK,CAAC;EACtC,CAAC,CAAC,CACD,QAAQ,SAAS,KAAK,SAAS,CAAC,CAAC,CACjC,IAAI,qBAEkC,CAAC,CAAC;CAC7C;AACF;;;AC/EA,IAAa,cAAb,cAAiC,gBAAgB;CAC/C,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC,EACvC,WAAW,KAAK,UAAU,KAAK,IAAI,EACrC;CAEA,YAAiD;EAC/C,MAAM,iBAAiB,KAAK,EAC1B,yDACF,CAAC,CACE,KAAK,GAAG,OAAO,gBAAgB,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAClD,IAAI,CAAC,CACL,QAAQ,SAAS,KAAK,SAAS,CAAC;EAEnC,OAAO,IAAI,IAAI,cAAc;CAC/B;AACF;;;ACnBA,IAAa,kBAAb,cAAqC,gBAAgB;CACnD,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,aAAyC,EACvC,aAAa,KAAK,YAAY,KAAK,IAAI,EACzC;CAEA,YACE,WAC6B;EAC7B,IAAI,aAAa,gBAAgB,SAAS,GACxC,OAAO;EAGT,MAAM,cACJ,KAAK,EAAE,4BAA0B,CAAC,CAAC,KAAK,SAAS,KACjD,KAAK,EAAE,mCAAiC,CAAC,CAAC,KAAK,SAAS,KACxD,KAAK,EAAE,iCAAiC,CAAC,CAAC,KAAK,KAC/C;EAEF,MAAM,aAAa,gBAAgB,WAAW;EAE9C,IAAI,CAAC,eAAe,CAAC,YACnB,MAAM,IAAI,MAAM,+BAA+B;EAGjD,OAAO;CACT;AACF;;;;;;;ACNA,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,MAAM,wBAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,2BAA2B,MAA4B;CAC9D,OAAO,cAAc,gBAAgB;EACnC,OAAO,OAAO;GACZ,OAAO;EACT;CACF;AACF;AAEA,MAAM,8BAA8B,sBAAsB,KAAK,SAC7D,2BAA2B,IAAI,CACjC;;;;;AAMA,MAAM,iBAAiB,EACrB,aAAa,YACf;AAEA,SAAS,qBACP,SACA,SAC8B;CAC9B,MAAM,WAAyC,CAAC;CAEhD,MAAM,gBACJ,MACA,SACA,WACG;EACH,MAAM,WAAW,SAAS;EAE1B,IAAI,YAAY,aAAa,SAC3B,MAAM,IAAI,MAAM,0BAA0B,KAAK,SAAS,OAAO,EAAE;EAGnE,SAAS,QAAQ;CACnB;CAEA,KAAK,MAAM,WAAW,SACpB,aAAa,QAAQ,KAAK,GAAG,SAAS,QAAQ;CAGhD,KAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,OAAO,GACnD,aAAa,OAAO,SAAS,OAAO;CAGtC,OAAO;AACT;;;;AAKA,MAAa,WAAW,qBACtB,CAAC,GAAG,sBAAsB,GAAG,2BAA2B,GACxD,cACF;;;ACxJA,IAAa,iBAAb,MAAa,uBAAuB,gBAAgB;CAClD,OAAO,OAAO;EACZ,OAAO;CACT;CAEA,UAAqC;EACnC,IAAI;GACF,OAAO,YAAY,KAAK,GAAG;EAC7B,QAAQ;GACN,OAAO,eAAe,KAAK;EAC7B;CACF;AACF;;;;;;;;ACsCA,SAAgB,WACd,KACA,EAAE,WAAW,UAA6B,CAAC,GAC3C;CACA,MAAM,WAAW,YAAY,GAAG;CAChC,MAAM,UAAU,SAAS;CAEzB,IAAI,SACF,OAAO;CAGT,IAAI,UACF,OAAO;CAGT,MAAM,IAAI,MACR,gBAAgB,SAAS,sFAC3B;AACF;AAgBA,eAAsB,aACpB,MACA,KACA,EACE,YAAY,OACZ,WAAW,MACX,GAAG,mBACmD,CAAC,GACF;CAEvD,MAAM,UAAU,KADA,WAAW,KAAK,EAAE,SAAS,CACjB,GAAE,MAAM,KAAK,cAAc;CACrD,OAAO,YAAY,QAAQ,UAAU,IAAI,QAAQ,MAAM;AACzD"}