@c-code/c-code-fw 1.2.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +104 -0
  2. package/fesm2022/c-code-c-code-fw-ui.mjs +815 -0
  3. package/fesm2022/c-code-c-code-fw-ui.mjs.map +1 -0
  4. package/package.json +9 -2
  5. package/ui/index.d.ts +5 -0
  6. package/ui/lib/components/category-menu/category-menu.component.d.ts +19 -0
  7. package/ui/lib/components/faq/faq.component.d.ts +16 -0
  8. package/ui/lib/components/gallery/gallery.component.d.ts +27 -0
  9. package/ui/lib/components/info-item/info-item.component.d.ts +18 -0
  10. package/ui/lib/components/lightbox/lightbox.component.d.ts +26 -0
  11. package/ui/lib/components/page-banner/page-banner.component.d.ts +23 -0
  12. package/ui/lib/components/plan-card/plan-card.component.d.ts +23 -0
  13. package/ui/lib/components/plan-catalog/plan-catalog.component.d.ts +78 -0
  14. package/ui/lib/components/plan-details/plan-details.component.d.ts +44 -0
  15. package/ui/lib/components/plan-filter-form/plan-filter-form.component.d.ts +28 -0
  16. package/ui/lib/components/promo-modal/promo-modal.component.d.ts +24 -0
  17. package/ui/lib/components/search-box/search-box.component.d.ts +18 -0
  18. package/ui/lib/components/social-links/social-links.component.d.ts +17 -0
  19. package/ui/lib/components/whatsapp-button/whatsapp-button.component.d.ts +19 -0
  20. package/ui/lib/models/plan.models.d.ts +50 -0
  21. package/ui/lib/models/ui.models.d.ts +24 -0
  22. package/ui/lib/services/plan-catalog.service.d.ts +43 -0
  23. package/ui/lib/services/seo.service.d.ts +39 -0
  24. package/ui/lib/utils/plan.utils.d.ts +16 -0
  25. package/ui/lib/utils/text.utils.d.ts +15 -0
  26. package/ui/public-api.d.ts +20 -0
  27. package/ui/theme/tokens.css +45 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"c-code-c-code-fw-ui.mjs","sources":["../../../projects/core/ui/src/lib/models/plan.models.ts","../../../projects/core/ui/src/lib/utils/text.utils.ts","../../../projects/core/ui/src/lib/utils/plan.utils.ts","../../../projects/core/ui/src/lib/services/plan-catalog.service.ts","../../../projects/core/ui/src/lib/services/seo.service.ts","../../../projects/core/ui/src/lib/components/whatsapp-button/whatsapp-button.component.ts","../../../projects/core/ui/src/lib/components/whatsapp-button/whatsapp-button.component.html","../../../projects/core/ui/src/lib/components/promo-modal/promo-modal.component.ts","../../../projects/core/ui/src/lib/components/promo-modal/promo-modal.component.html","../../../projects/core/ui/src/lib/components/page-banner/page-banner.component.ts","../../../projects/core/ui/src/lib/components/page-banner/page-banner.component.html","../../../projects/core/ui/src/lib/components/info-item/info-item.component.ts","../../../projects/core/ui/src/lib/components/info-item/info-item.component.html","../../../projects/core/ui/src/lib/components/faq/faq.component.ts","../../../projects/core/ui/src/lib/components/faq/faq.component.html","../../../projects/core/ui/src/lib/components/social-links/social-links.component.ts","../../../projects/core/ui/src/lib/components/social-links/social-links.component.html","../../../projects/core/ui/src/lib/components/plan-card/plan-card.component.ts","../../../projects/core/ui/src/lib/components/plan-card/plan-card.component.html","../../../projects/core/ui/src/lib/components/category-menu/category-menu.component.ts","../../../projects/core/ui/src/lib/components/category-menu/category-menu.component.html","../../../projects/core/ui/src/lib/components/search-box/search-box.component.ts","../../../projects/core/ui/src/lib/components/search-box/search-box.component.html","../../../projects/core/ui/src/lib/components/plan-filter-form/plan-filter-form.component.ts","../../../projects/core/ui/src/lib/components/plan-filter-form/plan-filter-form.component.html","../../../projects/core/ui/src/lib/components/plan-catalog/plan-catalog.component.ts","../../../projects/core/ui/src/lib/components/plan-catalog/plan-catalog.component.html","../../../projects/core/ui/src/lib/components/plan-details/plan-details.component.ts","../../../projects/core/ui/src/lib/components/plan-details/plan-details.component.html","../../../projects/core/ui/src/lib/components/lightbox/lightbox.component.ts","../../../projects/core/ui/src/lib/components/lightbox/lightbox.component.html","../../../projects/core/ui/src/lib/components/gallery/gallery.component.ts","../../../projects/core/ui/src/lib/components/gallery/gallery.component.html","../../../projects/core/ui/src/public-api.ts","../../../projects/core/ui/src/c-code-c-code-fw-ui.ts"],"sourcesContent":["/**\n * Plan category. The numeric values match the `category` field stored in the sites' `plans.json`.\n */\nexport enum PlanCategory {\n Individual = 0,\n Couple = 1,\n Group = 2,\n None = 3,\n}\n\n/** Add-on service that a plan includes (an entry in `additionals.json`). */\nexport interface AdditionalService {\n id: number;\n iconPath: string;\n name: string;\n}\n\n/** A spa plan (an entry in `plans.json`). */\nexport interface Plan {\n id: number;\n name: string;\n description: string;\n duration: string;\n price: number;\n /** Number of people the plan is for. The spelling matches the existing JSON files. */\n cuantity: number;\n imgPath: string;\n category: PlanCategory | number;\n additionalServicesId: number[];\n /** Filled in by `PlanCatalogService` by joining `additionalServicesId` with `additionals.json`. */\n additionalServices?: AdditionalService[];\n}\n\n/** Price range option for the filter form (an entry in `priceRanges.json`). */\nexport interface PriceRange {\n description: string;\n min: number;\n max: number;\n}\n\n/** Filter produced by `cc-plan-filter-form`. Empty fields are not applied. */\nexport interface PlanFilter {\n service?: AdditionalService | null;\n priceRange?: PriceRange | null;\n}\n\n/** Entry in `cc-category-menu`. */\nexport interface CategoryOption<T = PlanCategory | number> {\n value: T;\n label: string;\n /** Number shown next to the label. Leave it empty to hide it. */\n count?: number;\n}\n\n/** Default Spanish labels for the plan categories. */\nexport const DEFAULT_PLAN_CATEGORIES: CategoryOption[] = [\n { value: PlanCategory.Individual, label: 'Individual' },\n { value: PlanCategory.Couple, label: 'Pareja' },\n { value: PlanCategory.Group, label: 'Grupal' },\n];\n","/**\n * Turns a text into a URL-safe slug: \"PLAN FLOR DE LOTTO 3\" -> \"plan-flor-de-lotto-3\".\n * Accents are removed, so \"Jacuzzi Mágico\" -> \"jacuzzi-magico\".\n */\nexport function slugify(text: string): string {\n return text\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** \"PLAN GARDENIA\" -> \"Plan Gardenia\". */\nexport function titleCase(text: string): string {\n return text.toLowerCase().replace(/(^|\\s)\\S/g, (c) => c.toUpperCase());\n}\n\n/** Cuts a text at the last whole word that fits in `max` characters and adds \"…\". */\nexport function truncateText(text: string, max = 160): string {\n if (text.length <= max) return text;\n const cut = text.lastIndexOf(' ', max - 1);\n return text.slice(0, cut > 0 ? cut : max - 1) + '…';\n}\n\n/**\n * Builds a WhatsApp chat link.\n * @param phone Number with country code, with or without \"+\" and spaces: \"57 310 494 8884\".\n * @param message Pre-filled message, optional.\n */\nexport function whatsappUrl(phone: string, message?: string): string {\n const params = new URLSearchParams({ phone: phone.replace(/\\D/g, '') });\n if (message) params.set('text', message);\n params.set('type', 'phone_number');\n params.set('app_absent', '0');\n return `https://api.whatsapp.com/send/?${params.toString()}`;\n}\n","import {\n AdditionalService,\n CategoryOption,\n Plan,\n PlanCategory,\n PlanFilter,\n} from '../models/plan.models';\nimport { slugify } from './text.utils';\n\n/** Fills `plan.additionalServices` from `additionalServicesId`. Unknown ids are skipped. */\nexport function joinAdditionalServices(\n plans: Plan[],\n additionals: AdditionalService[]\n): Plan[] {\n const byId = new Map<number, AdditionalService>();\n // First entry wins, so a repeated id in the JSON keeps the original behavior of `Array.find`.\n for (const service of additionals) {\n if (!byId.has(service.id)) byId.set(service.id, service);\n }\n return plans.map((plan) => ({\n ...plan,\n additionalServices: plan.additionalServicesId\n .map((id) => byId.get(id))\n .filter((s): s is AdditionalService => !!s),\n }));\n}\n\n/** URL slug of a plan, derived from its name. */\nexport function planSlug(plan: Plan | string): string {\n return slugify(typeof plan === 'string' ? plan : plan.name);\n}\n\nexport function findPlanBySlug(plans: Plan[], slug: string): Plan | undefined {\n return plans.find((plan) => planSlug(plan) === slug);\n}\n\nexport function filterPlansByCategory(\n plans: Plan[],\n category: PlanCategory | number\n): Plan[] {\n return plans.filter((plan) => plan.category === category);\n}\n\n/** Case- and accent-insensitive search on the plan name. An empty query returns every plan. */\nexport function searchPlansByName(plans: Plan[], query: string): Plan[] {\n const q = slugify(query);\n if (!q) return plans;\n return plans.filter((plan) => planSlug(plan).includes(q));\n}\n\n/**\n * Applies a `PlanFilter`. Price uses `min < price <= max`, the same rule the sites had,\n * except that `min = 0` also includes free plans.\n */\nexport function filterPlans(plans: Plan[], filter: PlanFilter): Plan[] {\n const { service, priceRange } = filter;\n return plans.filter((plan) => {\n const matchesService =\n !service ||\n (plan.additionalServices ?? []).some((s) => s.id === service.id) ||\n plan.additionalServicesId.includes(service.id);\n const matchesPrice =\n !priceRange ||\n (plan.price <= priceRange.max &&\n (plan.price > priceRange.min || (priceRange.min === 0 && plan.price === 0)));\n return matchesService && matchesPrice;\n });\n}\n\n/** Returns the options with `count` set to the number of plans in each category. */\nexport function withCategoryCounts<T extends PlanCategory | number>(\n options: CategoryOption<T>[],\n plans: Plan[]\n): CategoryOption<T>[] {\n return options.map((option) => ({\n ...option,\n count: plans.filter((plan) => plan.category === option.value).length,\n }));\n}\n","import { HttpClient } from '@angular/common/http';\nimport {\n EnvironmentProviders,\n inject,\n Injectable,\n InjectionToken,\n makeEnvironmentProviders,\n} from '@angular/core';\nimport { forkJoin, map, Observable, of, shareReplay } from 'rxjs';\nimport {\n AdditionalService,\n Plan,\n PlanCategory,\n PlanFilter,\n PriceRange,\n} from '../models/plan.models';\nimport {\n filterPlans,\n filterPlansByCategory,\n findPlanBySlug,\n joinAdditionalServices,\n searchPlansByName,\n} from '../utils/plan.utils';\n\n/** Where `PlanCatalogService` loads the JSON files from. */\nexport interface PlanCatalogConfig {\n plansUrl: string;\n additionalsUrl: string;\n /** Leave it empty if the site has no `priceRanges.json`; `getPriceRanges()` then returns `[]`. */\n priceRangesUrl?: string | null;\n}\n\nexport const DEFAULT_PLAN_CATALOG_CONFIG: PlanCatalogConfig = {\n plansUrl: 'assets/data/plans.json',\n additionalsUrl: 'assets/data/additionals.json',\n priceRangesUrl: 'assets/data/priceRanges.json',\n};\n\nexport const PLAN_CATALOG_CONFIG = new InjectionToken<PlanCatalogConfig>(\n 'PLAN_CATALOG_CONFIG',\n { providedIn: 'root', factory: () => DEFAULT_PLAN_CATALOG_CONFIG }\n);\n\n/**\n * Overrides the JSON locations. Without it, the service reads `assets/data/*.json`.\n *\n * ```ts\n * providers: [provideHttpClient(withFetch()), providePlanCatalog({ priceRangesUrl: null })]\n * ```\n */\nexport function providePlanCatalog(\n config: Partial<PlanCatalogConfig> = {}\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: PLAN_CATALOG_CONFIG,\n useValue: { ...DEFAULT_PLAN_CATALOG_CONFIG, ...config },\n },\n ]);\n}\n\n/**\n * Reads plans, add-on services and price ranges from static JSON.\n * Each file is requested once and cached; filtering happens in memory.\n * Needs `provideHttpClient()` in the app.\n */\n@Injectable({ providedIn: 'root' })\nexport class PlanCatalogService {\n private http = inject(HttpClient);\n private config = inject(PLAN_CATALOG_CONFIG);\n\n private additionals$ = this.http\n .get<AdditionalService[]>(this.config.additionalsUrl)\n .pipe(shareReplay(1));\n\n private plans$ = forkJoin({\n plans: this.http.get<Plan[]>(this.config.plansUrl),\n additionals: this.additionals$,\n }).pipe(\n map(({ plans, additionals }) => joinAdditionalServices(plans, additionals)),\n shareReplay(1)\n );\n\n private priceRanges$ = (\n this.config.priceRangesUrl\n ? this.http.get<PriceRange[]>(this.config.priceRangesUrl)\n : of<PriceRange[]>([])\n ).pipe(shareReplay(1));\n\n /** Every plan, with `additionalServices` filled in. */\n getPlans(): Observable<Plan[]> {\n return this.plans$;\n }\n\n getAdditionalServices(): Observable<AdditionalService[]> {\n return this.additionals$;\n }\n\n getPriceRanges(): Observable<PriceRange[]> {\n return this.priceRanges$;\n }\n\n getPlansByCategory(category: PlanCategory | number): Observable<Plan[]> {\n return this.plans$.pipe(map((plans) => filterPlansByCategory(plans, category)));\n }\n\n getPlansByFilter(filter: PlanFilter): Observable<Plan[]> {\n return this.plans$.pipe(map((plans) => filterPlans(plans, filter)));\n }\n\n getPlansByName(name: string): Observable<Plan[]> {\n return this.plans$.pipe(map((plans) => searchPlansByName(plans, name)));\n }\n\n getPlanBySlug(slug: string): Observable<Plan | undefined> {\n return this.plans$.pipe(map((plans) => findPlanBySlug(plans, slug)));\n }\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n EnvironmentProviders,\n inject,\n Injectable,\n InjectionToken,\n makeEnvironmentProviders,\n} from '@angular/core';\nimport { Meta, Title } from '@angular/platform-browser';\n\nexport interface SeoConfig {\n /** Production domain without a trailing slash: \"https://www.laurelspamedellin.com\". */\n siteUrl: string;\n /** Image used when a page does not set one. Relative paths are resolved against `siteUrl`. */\n defaultImage?: string;\n}\n\nexport interface SeoData {\n title: string;\n description: string;\n /** Page path, for example \"/planes\". */\n path: string;\n /** Image for the share preview. Defaults to `SeoConfig.defaultImage`. */\n image?: string;\n}\n\nexport const SEO_CONFIG = new InjectionToken<SeoConfig>('SEO_CONFIG');\n\n/** Registers the site data `SeoService` needs. */\nexport function provideSeo(config: SeoConfig): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SEO_CONFIG,\n useValue: { ...config, siteUrl: config.siteUrl.replace(/\\/+$/, '') },\n },\n ]);\n}\n\n/**\n * Sets the title, description, canonical URL, Open Graph and Twitter tags, and JSON-LD blocks.\n * Works during SSR and prerendering. Requires `provideSeo()`.\n */\n@Injectable({ providedIn: 'root' })\nexport class SeoService {\n private title = inject(Title);\n private meta = inject(Meta);\n private document = inject(DOCUMENT);\n private config = inject(SEO_CONFIG);\n\n get siteUrl(): string {\n return this.config.siteUrl;\n }\n\n /** Turns a site path or relative asset into an absolute URL. Absolute URLs are returned unchanged. */\n absoluteUrl(pathOrUrl: string): string {\n if (/^https?:\\/\\//.test(pathOrUrl)) return pathOrUrl;\n return this.config.siteUrl + (pathOrUrl.startsWith('/') ? pathOrUrl : '/' + pathOrUrl);\n }\n\n update({ title, description, path, image }: SeoData): void {\n const url = this.absoluteUrl(path);\n const imageSrc = image ?? this.config.defaultImage;\n\n this.title.setTitle(title);\n this.meta.updateTag({ name: 'description', content: description });\n\n this.meta.updateTag({ property: 'og:title', content: title });\n this.meta.updateTag({ property: 'og:description', content: description });\n this.meta.updateTag({ property: 'og:url', content: url });\n\n this.meta.updateTag({ name: 'twitter:title', content: title });\n this.meta.updateTag({ name: 'twitter:description', content: description });\n\n if (imageSrc) {\n const imageUrl = this.absoluteUrl(imageSrc);\n this.meta.updateTag({ property: 'og:image', content: imageUrl });\n this.meta.updateTag({ name: 'twitter:image', content: imageUrl });\n }\n\n this.setCanonical(url);\n }\n\n /** Adds or replaces a `<script type=\"application/ld+json\">` with the given id. */\n setJsonLd(id: string, data: object): void {\n let script = this.document.getElementById(id);\n if (!script) {\n script = this.document.createElement('script');\n script.id = id;\n script.setAttribute('type', 'application/ld+json');\n this.document.head.appendChild(script);\n }\n script.textContent = JSON.stringify(data);\n }\n\n removeJsonLd(id: string): void {\n this.document.getElementById(id)?.remove();\n }\n\n private setCanonical(url: string): void {\n let link = this.document.head.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\n if (!link) {\n link = this.document.createElement('link');\n link.setAttribute('rel', 'canonical');\n this.document.head.appendChild(link);\n }\n link.setAttribute('href', url);\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\n/**\n * Floating WhatsApp button, fixed to a corner of the screen.\n *\n * ```html\n * <cc-whatsapp-button [href]=\"whatsappLink\" />\n * <cc-whatsapp-button [href]=\"whatsappLink\" iconSrc=\"assets/icons/whatsapp.png\" position=\"left\" />\n * ```\n */\n@Component({\n selector: 'cc-whatsapp-button',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './whatsapp-button.component.html',\n styleUrl: './whatsapp-button.component.css',\n})\nexport class WhatsappButtonComponent {\n /** WhatsApp link. Build it with `whatsappUrl(phone, message)`. */\n readonly href = input.required<string>();\n /** Custom icon. Without it, the WhatsApp logo is drawn as SVG. */\n readonly iconSrc = input<string>('');\n readonly label = input<string>('Escríbenos por WhatsApp');\n readonly position = input<'right' | 'left'>('right');\n}\n","<a\n class=\"cc-wa\"\n [class.cc-wa--left]=\"position() === 'left'\"\n [href]=\"href()\"\n target=\"_blank\"\n rel=\"noopener\"\n [attr.aria-label]=\"label()\"\n>\n @if (iconSrc()) {\n <img [src]=\"iconSrc()\" alt=\"\" class=\"cc-wa__img\" />\n } @else {\n <svg class=\"cc-wa__svg\" viewBox=\"0 0 32 32\" aria-hidden=\"true\">\n <circle cx=\"16\" cy=\"16\" r=\"16\" fill=\"#25D366\" />\n <path\n fill=\"#fff\"\n d=\"M23.3 8.7A10.3 10.3 0 0 0 7 21.1L5.6 26.4l5.4-1.4a10.3 10.3 0 0 0 4.9 1.3 10.3 10.3 0 0 0 7.4-17.6Zm-7.3 15.8a8.5 8.5 0 0 1-4.3-1.2l-.3-.2-3.2.8.9-3.1-.2-.3a8.5 8.5 0 1 1 7.1 4Zm4.7-6.4c-.3-.1-1.5-.7-1.8-.8-.2-.1-.4-.1-.6.1l-.8 1c-.2.2-.3.2-.6.1a7 7 0 0 1-3.5-3c-.3-.5.3-.4.7-1.4.1-.2 0-.3 0-.4l-.8-2c-.2-.5-.4-.4-.6-.4h-.5a1 1 0 0 0-.7.3 3 3 0 0 0-.9 2.2 5.2 5.2 0 0 0 1.1 2.8 11.9 11.9 0 0 0 4.6 4c1.7.7 2.4.8 3.2.7a2.8 2.8 0 0 0 1.8-1.3 2.3 2.3 0 0 0 .2-1.3c-.1-.1-.3-.2-.6-.3Z\"\n />\n </svg>\n }\n</a>\n","import {\n ChangeDetectionStrategy,\n Component,\n input,\n model,\n output,\n} from '@angular/core';\n\n/**\n * Modal over the page, for example a promotion shown when the site opens.\n * Shows `imageSrc`, or the projected content when there is no image.\n * Closes with the ✕ button, a backdrop click or the Escape key.\n *\n * ```html\n * <cc-promo-modal [(open)]=\"showPromo\" imageSrc=\"assets/images/pop.jpeg\" imageAlt=\"Promoción\" />\n * ```\n */\n@Component({\n selector: 'cc-promo-modal',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { '(document:keydown.escape)': 'open() && close()' },\n templateUrl: './promo-modal.component.html',\n styleUrl: './promo-modal.component.css',\n})\nexport class PromoModalComponent {\n /** Whether the modal is visible. Use `[(open)]` to bind both ways. */\n readonly open = model<boolean>(true);\n readonly imageSrc = input<string>('');\n readonly imageAlt = input<string>('');\n /** Makes the image a link, for example to WhatsApp. */\n readonly link = input<string>('');\n readonly closeOnBackdrop = input<boolean>(true);\n readonly closeLabel = input<string>('Cerrar');\n readonly closed = output<void>();\n\n close(): void {\n this.open.set(false);\n this.closed.emit();\n }\n}\n","@if (open()) {\n <div class=\"cc-modal\" (click)=\"closeOnBackdrop() && close()\">\n <div\n class=\"cc-modal__box\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"imageAlt() || null\"\n (click)=\"$event.stopPropagation()\"\n >\n <button type=\"button\" class=\"cc-modal__close\" [attr.aria-label]=\"closeLabel()\" (click)=\"close()\">\n ✕\n </button>\n @if (imageSrc()) {\n @if (link()) {\n <a [href]=\"link()\" target=\"_blank\" rel=\"noopener\">\n <img [src]=\"imageSrc()\" [alt]=\"imageAlt()\" class=\"cc-modal__img\" />\n </a>\n } @else {\n <img [src]=\"imageSrc()\" [alt]=\"imageAlt()\" class=\"cc-modal__img\" />\n }\n } @else {\n <ng-content />\n }\n </div>\n </div>\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { LinkTarget } from '../../models/ui.models';\n\n/**\n * Title strip at the top of a page, with an optional back link and icon.\n * Renders the page's `<h1>`.\n *\n * ```html\n * <cc-page-banner title=\"Reserva tu plan - Pareja\" />\n * <cc-page-banner [title]=\"plan.name\" backLink=\"/planes\" iconSrc=\"assets/icons/hoja.png\" variant=\"plain\" />\n * ```\n */\n@Component({\n selector: 'cc-page-banner',\n imports: [RouterLink],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './page-banner.component.html',\n styleUrl: './page-banner.component.css',\n})\nexport class PageBannerComponent {\n readonly title = input.required<string>();\n /** `band` uses the surface color; `plain` uses the page background. */\n readonly variant = input<'band' | 'plain'>('band');\n readonly iconSrc = input<string>('');\n /** Shows a back arrow that links here. */\n readonly backLink = input<LinkTarget | null>(null);\n readonly backIconSrc = input<string>('');\n readonly backLabel = input<string>('Volver');\n}\n","<div class=\"cc-banner\" [class.cc-banner--plain]=\"variant() === 'plain'\" [class.cc-banner--split]=\"!!backLink()\">\n @if (backLink()) {\n <a class=\"cc-banner__back\" [routerLink]=\"backLink()\" [attr.aria-label]=\"backLabel()\">\n @if (backIconSrc()) {\n <img [src]=\"backIconSrc()\" alt=\"\" />\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M15 18l-6-6 6-6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </a>\n }\n <div class=\"cc-banner__title\">\n <h1>{{ title() }}</h1>\n @if (iconSrc()) {\n <img [src]=\"iconSrc()\" alt=\"\" class=\"cc-banner__icon\" />\n }\n </div>\n @if (backLink()) {\n <span class=\"cc-banner__spacer\"></span>\n }\n</div>\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\n/**\n * Title with a paragraph, used for policy sections.\n * Extra content (lists, notes) can be projected under the text.\n *\n * ```html\n * <cc-info-item title=\"Pagos\" text=\"Aceptamos Nequi, Daviplata y efectivo.\" />\n * <cc-info-item title=\"Reservaciones\" [text]=\"texto\">\n * <ul><li>…</li></ul>\n * </cc-info-item>\n * ```\n */\n@Component({\n selector: 'cc-info-item',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './info-item.component.html',\n styleUrl: './info-item.component.css',\n})\nexport class InfoItemComponent {\n readonly title = input.required<string>();\n readonly text = input<string>('');\n}\n","<h2 class=\"cc-info__title\">{{ title() }}</h2>\n@if (text()) {\n <p class=\"cc-info__text\">{{ text() }}</p>\n}\n<ng-content />\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FaqItem } from '../../models/ui.models';\n\n/**\n * Frequently asked questions as native `<details>` elements, so the answers\n * stay in the prerendered HTML for search engines.\n *\n * ```html\n * <cc-faq title=\"PREGUNTAS FRECUENTES\" [items]=\"faq\" />\n * ```\n */\n@Component({\n selector: 'cc-faq',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './faq.component.html',\n styleUrl: './faq.component.css',\n})\nexport class FaqComponent {\n readonly items = input.required<FaqItem[]>();\n readonly title = input<string>('');\n}\n","@if (title()) {\n <h2 class=\"cc-faq__title\">{{ title() }}</h2>\n}\n@for (item of items(); track item.question) {\n <details class=\"cc-faq__item\">\n <summary>{{ item.question }}</summary>\n <p>{{ item.answer }}</p>\n </details>\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { SocialLink } from '../../models/ui.models';\n\n/**\n * Row of social network icons that open in a new tab.\n *\n * ```html\n * <cc-social-links [links]=\"socials\" size=\"1.25rem\" />\n * ```\n */\n@Component({\n selector: 'cc-social-links',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './social-links.component.html',\n styleUrl: './social-links.component.css',\n})\nexport class SocialLinksComponent {\n readonly links = input.required<SocialLink[]>();\n /** Icon width, any CSS length. */\n readonly size = input<string>('1rem');\n readonly gap = input<string>('1.25rem');\n}\n","<ul class=\"cc-social\" [style.gap]=\"gap()\">\n @for (link of links(); track link.href) {\n <li>\n <a [href]=\"link.href\" target=\"_blank\" rel=\"noopener\" [attr.aria-label]=\"link.label\">\n <img [src]=\"link.iconSrc\" alt=\"\" [style.width]=\"size()\" />\n </a>\n </li>\n }\n</ul>\n","import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { LinkTarget } from '../../models/ui.models';\n\n/**\n * Plan card: image, name and a button.\n * With `link` the button navigates with the router; without it, it emits `ctaClick`.\n *\n * ```html\n * <cc-plan-card [name]=\"plan.name\" [imageSrc]=\"plan.imgPath\" [link]=\"['/planes', slug]\" />\n * ```\n */\n@Component({\n selector: 'cc-plan-card',\n imports: [RouterLink],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './plan-card.component.html',\n styleUrl: './plan-card.component.css',\n})\nexport class PlanCardComponent {\n readonly name = input.required<string>();\n readonly imageSrc = input.required<string>();\n /** Defaults to `name`. */\n readonly imageAlt = input<string>('');\n /** Optional line under the name, for example the formatted price. */\n readonly subtitle = input<string>('');\n readonly link = input<LinkTarget | null>(null);\n readonly ctaLabel = input<string>('Saber más');\n readonly ctaClick = output<void>();\n}\n","<article class=\"cc-card\">\n <img\n class=\"cc-card__img\"\n [src]=\"imageSrc()\"\n [alt]=\"imageAlt() || name()\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n <h2 class=\"cc-card__name\">{{ name() }}</h2>\n @if (subtitle()) {\n <p class=\"cc-card__subtitle\">{{ subtitle() }}</p>\n }\n @if (link()) {\n <a class=\"cc-card__cta\" [routerLink]=\"link()\" (click)=\"ctaClick.emit()\">{{ ctaLabel() }}</a>\n } @else {\n <button type=\"button\" class=\"cc-card__cta\" (click)=\"ctaClick.emit()\">{{ ctaLabel() }}</button>\n }\n</article>\n","import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';\nimport { CategoryOption } from '../../models/plan.models';\n\n/**\n * Vertical category list with a single selected option.\n * Replaces the `appElementActive` markup the sites used for plan categories.\n *\n * ```html\n * <cc-category-menu [options]=\"categories\" [(selected)]=\"category\" />\n * ```\n */\n@Component({\n selector: 'cc-category-menu',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './category-menu.component.html',\n styleUrl: './category-menu.component.css',\n})\nexport class CategoryMenuComponent<T = unknown> {\n readonly options = input.required<CategoryOption<T>[]>();\n /** Selected value. Use `[(selected)]` to bind both ways. */\n readonly selected = model<T | null>(null);\n readonly title = input<string>('CATEGORÍA DE PLANES');\n\n select(value: T): void {\n this.selected.set(value);\n }\n}\n","<ul class=\"cc-cat\" role=\"listbox\" [attr.aria-label]=\"title() || null\">\n @if (title()) {\n <li class=\"cc-cat__title\" role=\"presentation\">{{ title() }}</li>\n }\n @for (option of options(); track option.value) {\n <li\n class=\"cc-cat__item\"\n role=\"option\"\n tabindex=\"0\"\n [class.cc-cat__item--active]=\"option.value === selected()\"\n [attr.aria-selected]=\"option.value === selected()\"\n (click)=\"select(option.value)\"\n (keydown.enter)=\"select(option.value)\"\n (keydown.space)=\"$event.preventDefault(); select(option.value)\"\n >\n <span>{{ option.label }}</span>\n @if (option.count !== undefined && option.count !== null) {\n <small>{{ option.count }}</small>\n }\n </li>\n }\n</ul>\n","import { ChangeDetectionStrategy, Component, input, model, output } from '@angular/core';\n\n/**\n * Text field with a search button. Emits `search` on Enter or on the button.\n *\n * ```html\n * <cc-search-box placeholder=\"Buscar plan\" [(value)]=\"query\" (search)=\"onSearch($event)\" />\n * ```\n */\n@Component({\n selector: 'cc-search-box',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './search-box.component.html',\n styleUrl: './search-box.component.css',\n})\nexport class SearchBoxComponent {\n readonly value = model<string>('');\n readonly placeholder = input<string>('Buscar plan');\n readonly buttonLabel = input<string>('Buscar');\n readonly iconSrc = input<string>('');\n readonly search = output<string>();\n\n onInput(event: Event): void {\n this.value.set((event.target as HTMLInputElement).value);\n }\n}\n","<form class=\"cc-search\" role=\"search\" (submit)=\"$event.preventDefault(); search.emit(value())\">\n <input\n type=\"search\"\n class=\"cc-search__input\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"placeholder()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n />\n <button type=\"submit\" class=\"cc-search__btn\" [attr.aria-label]=\"buttonLabel()\">\n @if (iconSrc()) {\n <img [src]=\"iconSrc()\" alt=\"\" />\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" />\n <path d=\"m20 20-3.5-3.5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n }\n </button>\n</form>\n","import { ChangeDetectionStrategy, Component, input, output, signal } from '@angular/core';\nimport { AdditionalService, PlanFilter, PriceRange } from '../../models/plan.models';\n\n/**\n * Filter by included service and price range. The options come in as inputs,\n * so the form does not load data by itself.\n *\n * ```html\n * <cc-plan-filter-form [services]=\"services\" [priceRanges]=\"ranges\" (filterChange)=\"apply($event)\" />\n * ```\n */\n@Component({\n selector: 'cc-plan-filter-form',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './plan-filter-form.component.html',\n styleUrl: './plan-filter-form.component.css',\n})\nexport class PlanFilterFormComponent {\n readonly services = input<AdditionalService[]>([]);\n readonly priceRanges = input<PriceRange[]>([]);\n readonly serviceLabel = input<string>('Servicio incluido');\n readonly priceLabel = input<string>('Rango de precio');\n readonly anyLabel = input<string>('Todos');\n readonly submitLabel = input<string>('Filtrar');\n readonly resetLabel = input<string>('Limpiar');\n /** Emits on submit, and with an empty filter on reset. */\n readonly filterChange = output<PlanFilter>();\n\n protected serviceIndex = signal(-1);\n protected rangeIndex = signal(-1);\n\n protected toIndex(event: Event): number {\n return Number((event.target as HTMLSelectElement).value);\n }\n\n submit(): void {\n this.filterChange.emit({\n service: this.services()[this.serviceIndex()] ?? null,\n priceRange: this.priceRanges()[this.rangeIndex()] ?? null,\n });\n }\n\n reset(): void {\n this.serviceIndex.set(-1);\n this.rangeIndex.set(-1);\n this.filterChange.emit({ service: null, priceRange: null });\n }\n}\n","<form class=\"cc-filter\" (submit)=\"$event.preventDefault(); submit()\">\n @if (services().length) {\n <label class=\"cc-filter__field\">\n <span>{{ serviceLabel() }}</span>\n <select (change)=\"serviceIndex.set(toIndex($event))\">\n <option value=\"-1\" [selected]=\"serviceIndex() === -1\">{{ anyLabel() }}</option>\n @for (service of services(); track $index) {\n <option [value]=\"$index\" [selected]=\"serviceIndex() === $index\">{{ service.name }}</option>\n }\n </select>\n </label>\n }\n @if (priceRanges().length) {\n <label class=\"cc-filter__field\">\n <span>{{ priceLabel() }}</span>\n <select (change)=\"rangeIndex.set(toIndex($event))\">\n <option value=\"-1\" [selected]=\"rangeIndex() === -1\">{{ anyLabel() }}</option>\n @for (range of priceRanges(); track $index) {\n <option [value]=\"$index\" [selected]=\"rangeIndex() === $index\">{{ range.description }}</option>\n }\n </select>\n </label>\n }\n <div class=\"cc-filter__actions\">\n <button type=\"submit\" class=\"cc-filter__submit\">{{ submitLabel() }}</button>\n <button type=\"button\" class=\"cc-filter__reset\" (click)=\"reset()\">{{ resetLabel() }}</button>\n </div>\n</form>\n","import { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n contentChild,\n Directive,\n effect,\n input,\n output,\n signal,\n TemplateRef,\n untracked,\n} from '@angular/core';\nimport {\n AdditionalService,\n CategoryOption,\n DEFAULT_PLAN_CATEGORIES,\n Plan,\n PlanCategory,\n PlanFilter,\n PriceRange,\n} from '../../models/plan.models';\nimport { LinkTarget } from '../../models/ui.models';\nimport {\n filterPlans,\n filterPlansByCategory,\n planSlug,\n searchPlansByName,\n withCategoryCounts,\n} from '../../utils/plan.utils';\nimport { CategoryMenuComponent } from '../category-menu/category-menu.component';\nimport { PageBannerComponent } from '../page-banner/page-banner.component';\nimport { PlanCardComponent } from '../plan-card/plan-card.component';\nimport { PlanFilterFormComponent } from '../plan-filter-form/plan-filter-form.component';\nimport { SearchBoxComponent } from '../search-box/search-box.component';\n\n/** Context of a custom card template: `<ng-template ccPlanCard let-plan let-link=\"link\">`. */\nexport interface PlanCardContext {\n $implicit: Plan;\n link: LinkTarget;\n}\n\n/** Marks an `<ng-template>` inside `cc-plan-catalog` as the card to render for each plan. */\n@Directive({ selector: 'ng-template[ccPlanCard]' })\nexport class PlanCardTemplateDirective {\n static ngTemplateContextGuard(_dir: PlanCardTemplateDirective, ctx: unknown): ctx is PlanCardContext {\n return true;\n }\n}\n\n/**\n * Full plan list page: title, name search, category menu, filter and card grid.\n * It only needs the plans; the rest has Spanish defaults you can override.\n *\n * Selecting a category shows its plans. Searching by name or applying the filter\n * looks across every category, as the sites already did.\n *\n * ```html\n * <cc-plan-catalog\n * [plans]=\"plans()\"\n * [services]=\"services()\"\n * [priceRanges]=\"ranges()\"\n * imageAltSuffix=\" - Laurel Spa Medellín\"\n * />\n * ```\n */\n@Component({\n selector: 'cc-plan-catalog',\n imports: [\n NgTemplateOutlet,\n PageBannerComponent,\n SearchBoxComponent,\n CategoryMenuComponent,\n PlanFilterFormComponent,\n PlanCardComponent,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './plan-catalog.component.html',\n styleUrl: './plan-catalog.component.css',\n})\nexport class PlanCatalogComponent {\n readonly plans = input.required<Plan[]>();\n readonly categories = input<CategoryOption[]>(DEFAULT_PLAN_CATEGORIES);\n readonly initialCategory = input<PlanCategory | number>(PlanCategory.Individual);\n /** Shows how many plans each category has, counted from `plans`. */\n readonly showCounts = input<boolean>(true);\n /** Options for the filter form. The filter is hidden when both are empty. */\n readonly services = input<AdditionalService[]>([]);\n readonly priceRanges = input<PriceRange[]>([]);\n /** Where each card links to. Defaults to `/planes/<slug>`. */\n readonly detailsLink = input<(plan: Plan) => LinkTarget>((plan) => ['/planes', planSlug(plan)]);\n\n readonly showBanner = input<boolean>(true);\n readonly showSearch = input<boolean>(true);\n readonly titlePrefix = input<string>('Reserva tu plan');\n readonly resultsTitle = input<string>('Resultados');\n readonly categoriesTitle = input<string>('CATEGORÍA DE PLANES');\n readonly searchPlaceholder = input<string>('Buscar plan');\n readonly searchIconSrc = input<string>('');\n readonly filterTitle = input<string>('Filtrar');\n readonly filterIconSrc = input<string>('');\n readonly ctaLabel = input<string>('Saber más');\n /** Added after the plan name in each image's alt text. */\n readonly imageAltSuffix = input<string>('');\n readonly emptyMessage = input<string>('No encontramos planes con esos criterios.');\n\n readonly categoryChange = output<PlanCategory | number>();\n\n protected readonly cardTemplate = contentChild(PlanCardTemplateDirective, { read: TemplateRef });\n\n protected readonly category = signal<PlanCategory | number | null>(null);\n protected readonly query = signal('');\n protected readonly filter = signal<PlanFilter | null>(null);\n protected readonly filterOpen = signal(false);\n /** Set when the user searched or filtered; results then cover every category. */\n protected readonly searching = signal(false);\n /** Category that was open before searching, to return to it when the search is cleared. */\n private lastCategory: PlanCategory | number | null = null;\n\n constructor() {\n // Apply `initialCategory` until the user picks one.\n effect(() => {\n const initial = this.initialCategory();\n untracked(() => {\n if (!this.searching()) this.category.set(initial);\n });\n });\n }\n\n protected readonly categoryOptions = computed(() =>\n this.showCounts() ? withCategoryCounts(this.categories(), this.plans()) : this.categories()\n );\n\n protected readonly hasFilter = computed(\n () => this.services().length > 0 || this.priceRanges().length > 0\n );\n\n protected readonly visiblePlans = computed(() => {\n const plans = this.plans();\n if (!this.searching()) {\n const category = this.category();\n return category === null ? plans : filterPlansByCategory(plans, category);\n }\n const byName = searchPlansByName(plans, this.query());\n const filter = this.filter();\n return filter ? filterPlans(byName, filter) : byName;\n });\n\n protected readonly title = computed(() => {\n if (this.searching()) return `${this.titlePrefix()} - ${this.resultsTitle()}`;\n const option = this.categories().find((c) => c.value === this.category());\n return option ? `${this.titlePrefix()} - ${option.label}` : this.titlePrefix();\n });\n\n protected selectCategory(value: PlanCategory | number | null): void {\n if (value === null) return;\n this.searching.set(false);\n this.query.set('');\n this.filter.set(null);\n this.category.set(value);\n this.categoryChange.emit(value);\n }\n\n protected onSearch(query: string): void {\n this.query.set(query);\n if (!query.trim() && !this.filter()) {\n this.selectCategory(this.lastCategory ?? this.initialCategory());\n return;\n }\n this.startSearch();\n }\n\n protected onFilter(filter: PlanFilter): void {\n const empty = !filter.service && !filter.priceRange;\n this.filter.set(empty ? null : filter);\n if (empty && !this.query().trim()) {\n this.selectCategory(this.lastCategory ?? this.initialCategory());\n return;\n }\n this.startSearch();\n }\n\n protected linkFor(plan: Plan): LinkTarget {\n return this.detailsLink()(plan);\n }\n\n private startSearch(): void {\n if (!this.searching()) this.lastCategory = this.category();\n this.searching.set(true);\n this.category.set(null);\n }\n}\n","@if (showBanner()) {\n <cc-page-banner [title]=\"title()\" />\n}\n\n<div class=\"cc-catalog\">\n <aside class=\"cc-catalog__side\">\n @if (showSearch()) {\n <cc-search-box\n [placeholder]=\"searchPlaceholder()\"\n [iconSrc]=\"searchIconSrc()\"\n [(value)]=\"query\"\n (search)=\"onSearch($event)\"\n />\n }\n\n <div class=\"cc-catalog__panel\">\n <cc-category-menu\n [title]=\"categoriesTitle()\"\n [options]=\"categoryOptions()\"\n [selected]=\"category()\"\n (selectedChange)=\"selectCategory($event)\"\n />\n\n @if (hasFilter()) {\n <hr class=\"cc-catalog__rule\" />\n <button\n type=\"button\"\n class=\"cc-catalog__filter-toggle\"\n [attr.aria-expanded]=\"filterOpen()\"\n (click)=\"filterOpen.set(!filterOpen())\"\n >\n <span>{{ filterTitle() }}</span>\n @if (filterIconSrc()) {\n <img [src]=\"filterIconSrc()\" alt=\"\" />\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M3 5h18l-7 8v6l-4-2v-4L3 5Z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linejoin=\"round\" />\n </svg>\n }\n </button>\n @if (filterOpen()) {\n <cc-plan-filter-form\n [services]=\"services()\"\n [priceRanges]=\"priceRanges()\"\n (filterChange)=\"onFilter($event)\"\n />\n }\n }\n </div>\n </aside>\n\n @if (visiblePlans().length) {\n <div class=\"cc-catalog__grid\">\n @for (plan of visiblePlans(); track plan.id) {\n @if (cardTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: plan, link: linkFor(plan) }\" />\n } @else {\n <cc-plan-card\n [name]=\"plan.name\"\n [imageSrc]=\"plan.imgPath\"\n [imageAlt]=\"plan.name + imageAltSuffix()\"\n [link]=\"linkFor(plan)\"\n [ctaLabel]=\"ctaLabel()\"\n />\n }\n }\n </div>\n } @else {\n <p class=\"cc-catalog__empty\">{{ emptyMessage() }}</p>\n }\n</div>\n","import { CurrencyPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { Plan } from '../../models/plan.models';\nimport { LinkTarget } from '../../models/ui.models';\nimport { PageBannerComponent } from '../page-banner/page-banner.component';\n\n/**\n * Plan detail view: title with a back link, photo, price, description, duration,\n * number of people, included services, and a gift box with the booking button.\n * It only renders; the page loads the plan and handles SEO.\n *\n * ```html\n * <cc-plan-details [plan]=\"plan\" [bookingUrl]=\"whatsappLink\" imageAltSuffix=\" en Laurel Spa Medellín\" />\n * ```\n */\n@Component({\n selector: 'cc-plan-details',\n imports: [CurrencyPipe, PageBannerComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './plan-details.component.html',\n styleUrl: './plan-details.component.css',\n})\nexport class PlanDetailsComponent {\n readonly plan = input.required<Plan>();\n /** Link of the booking button, usually WhatsApp. */\n readonly bookingUrl = input.required<string>();\n readonly bookingLabel = input<string>('¡RESERVAR AHORA!');\n\n readonly showBanner = input<boolean>(true);\n readonly backLink = input<LinkTarget | null>('/planes');\n readonly backLabel = input<string>('Volver a los planes');\n readonly backIconSrc = input<string>('');\n readonly titleIconSrc = input<string>('');\n /** Added after the plan name in the photo's alt text. */\n readonly imageAltSuffix = input<string>('');\n\n /** Arguments of Angular's `currency` pipe. */\n readonly currencyCode = input<string>('COP');\n readonly currencyDisplay = input<string>('COP$ ');\n readonly currencyDigits = input<string>('1.0-0');\n\n readonly durationIconSrc = input<string>('');\n readonly personIconSrc = input<string>('');\n readonly peopleIconSrc = input<string>('');\n readonly personLabel = input<string>('persona');\n readonly peopleLabel = input<string>('Personas');\n readonly includesLabel = input<string>('Incluye:');\n\n /** Gift box. It is hidden when `perks` is empty. */\n readonly perksTitle = input<string>('Recibe con nuestro plan');\n readonly perks = input<string[]>([\n 'Gift Card de invitación en PDF para regalo.',\n 'Decoración según la ocasión que celebres.',\n ]);\n readonly perksImageSrc = input<string>('');\n readonly perksImageAlt = input<string>('Gift card de regalo');\n\n protected readonly people = computed(() => Math.max(this.plan().cuantity || 1, 1));\n protected readonly services = computed(() => this.plan().additionalServices ?? []);\n}\n","@if (showBanner()) {\n <cc-page-banner\n variant=\"plain\"\n [title]=\"plan().name\"\n [iconSrc]=\"titleIconSrc()\"\n [backLink]=\"backLink()\"\n [backLabel]=\"backLabel()\"\n [backIconSrc]=\"backIconSrc()\"\n />\n}\n\n<div class=\"cc-details\">\n <div class=\"cc-details__media\">\n <img class=\"cc-details__photo\" [src]=\"plan().imgPath\" [alt]=\"plan().name + imageAltSuffix()\" />\n <ng-content select=\"[ccDetailsMedia]\" />\n </div>\n\n <div class=\"cc-details__panel\">\n <div class=\"cc-details__info\">\n <div class=\"cc-details__price\">\n <p>{{ plan().price | currency: currencyCode() : currencyDisplay() : currencyDigits() }}</p>\n </div>\n\n <p class=\"cc-details__description\">{{ plan().description }}</p>\n\n <div class=\"cc-details__facts\">\n <div class=\"cc-details__fact\">\n @if (durationIconSrc()) {\n <img [src]=\"durationIconSrc()\" alt=\"Duración\" />\n } @else {\n <svg viewBox=\"0 0 24 24\" role=\"img\" aria-label=\"Duración\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" />\n <path d=\"M12 7v5l3 2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n }\n <small>{{ plan().duration }}</small>\n </div>\n\n <div class=\"cc-details__fact\">\n @if (people() > 1) {\n @if (peopleIconSrc()) {\n <img [src]=\"peopleIconSrc()\" alt=\"\" />\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <circle cx=\"9\" cy=\"8\" r=\"3.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" />\n <circle cx=\"17\" cy=\"9\" r=\"2.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" />\n <path d=\"M2.5 20a6.5 6.5 0 0 1 13 0M15 14.5a5 5 0 0 1 6.5 4.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n }\n <small>{{ people() }} {{ peopleLabel() }}</small>\n } @else {\n @if (personIconSrc()) {\n <img [src]=\"personIconSrc()\" alt=\"\" />\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"8\" r=\"4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" />\n <path d=\"M4.5 20a7.5 7.5 0 0 1 15 0\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n }\n <small>1 {{ personLabel() }}</small>\n }\n </div>\n </div>\n\n @if (services().length) {\n <h2 class=\"cc-details__includes\">{{ includesLabel() }}</h2>\n <ul class=\"cc-details__services\">\n @for (service of services(); track $index) {\n <li>\n <img [src]=\"service.iconPath\" alt=\"\" />\n <small>{{ service.name }}</small>\n </li>\n }\n </ul>\n }\n\n <ng-content />\n\n <div class=\"cc-details__gift\" [class.cc-details__gift--solo]=\"!perks().length\">\n @if (perks().length) {\n @if (perksImageSrc()) {\n <img class=\"cc-details__gift-img\" [src]=\"perksImageSrc()\" [alt]=\"perksImageAlt()\" loading=\"lazy\" />\n }\n <div class=\"cc-details__perks\">\n <h2>{{ perksTitle() }}</h2>\n <ul>\n @for (perk of perks(); track $index) {\n <li>{{ perk }}</li>\n }\n </ul>\n </div>\n }\n <div class=\"cc-details__cta-wrap\">\n <a class=\"cc-details__cta\" [href]=\"bookingUrl()\" target=\"_blank\" rel=\"noopener\">{{ bookingLabel() }}</a>\n </div>\n </div>\n </div>\n </div>\n</div>\n","import { DOCUMENT } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n input,\n model,\n} from '@angular/core';\nimport { GalleryImage } from '../../models/ui.models';\n\n/**\n * Full-screen image viewer. It is open while `index` is not null.\n * Arrow keys move between images and Escape closes it.\n *\n * ```html\n * <cc-lightbox [images]=\"images\" [(index)]=\"openIndex\" />\n * ```\n */\n@Component({\n selector: 'cc-lightbox',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '(document:keydown.escape)': 'close()',\n '(document:keydown.arrowright)': 'next()',\n '(document:keydown.arrowleft)': 'prev()',\n },\n templateUrl: './lightbox.component.html',\n styleUrl: './lightbox.component.css',\n})\nexport class LightboxComponent {\n readonly images = input.required<GalleryImage[]>();\n /** Index of the open image, or null when closed. Use `[(index)]`. */\n readonly index = model<number | null>(null);\n readonly closeLabel = input<string>('Cerrar');\n readonly prevLabel = input<string>('Imagen anterior');\n readonly nextLabel = input<string>('Imagen siguiente');\n\n protected readonly current = computed(() => {\n const i = this.index();\n return i === null ? null : (this.images()[i] ?? null);\n });\n\n private document = inject(DOCUMENT);\n\n constructor() {\n // Stop the page behind from scrolling while the viewer is open.\n effect((onCleanup) => {\n if (this.current() === null) return;\n const body = this.document.body;\n const previous = body.style.overflow;\n body.style.overflow = 'hidden';\n onCleanup(() => (body.style.overflow = previous));\n });\n }\n\n close(): void {\n if (this.index() !== null) this.index.set(null);\n }\n\n next(): void {\n const i = this.index();\n const total = this.images().length;\n if (i !== null && total) this.index.set((i + 1) % total);\n }\n\n prev(): void {\n const i = this.index();\n const total = this.images().length;\n if (i !== null && total) this.index.set((i - 1 + total) % total);\n }\n}\n","@if (current(); as image) {\n <div class=\"cc-lb\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"image.caption || image.alt || null\" (click)=\"close()\">\n <button type=\"button\" class=\"cc-lb__btn cc-lb__close\" [attr.aria-label]=\"closeLabel()\" (click)=\"close()\">✕</button>\n @if (images().length > 1) {\n <button type=\"button\" class=\"cc-lb__btn cc-lb__prev\" [attr.aria-label]=\"prevLabel()\" (click)=\"$event.stopPropagation(); prev()\">‹</button>\n }\n <img class=\"cc-lb__img\" [src]=\"image.src\" [alt]=\"image.alt ?? image.caption ?? ''\" (click)=\"$event.stopPropagation()\" />\n @if (images().length > 1) {\n <button type=\"button\" class=\"cc-lb__btn cc-lb__next\" [attr.aria-label]=\"nextLabel()\" (click)=\"$event.stopPropagation(); next()\">›</button>\n }\n @if (image.caption) {\n <p class=\"cc-lb__caption\">{{ image.caption }}</p>\n }\n </div>\n}\n","import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core';\nimport { GalleryImage } from '../../models/ui.models';\nimport { LightboxComponent } from '../lightbox/lightbox.component';\n\n/**\n * Thumbnail grid that opens a lightbox on click.\n *\n * ```html\n * <cc-gallery [images]=\"images\" />\n * ```\n *\n * `numberedImages()` builds the list for folders named 1.jpeg, 2.jpeg, …\n */\n@Component({\n selector: 'cc-gallery',\n imports: [LightboxComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './gallery.component.html',\n styleUrl: './gallery.component.css',\n})\nexport class GalleryComponent {\n readonly images = input.required<GalleryImage[]>();\n readonly backgroundImage = input<string>('');\n readonly openLabel = input<string>('Ver imagen');\n\n protected readonly open = signal<number | null>(null);\n}\n\n/**\n * Builds a gallery list for numbered files.\n *\n * ```ts\n * numberedImages(18, (i) => `assets/images/galery/${i}.jpeg`, (i) => `Instalaciones - foto ${i}`)\n * ```\n */\nexport function numberedImages(\n count: number,\n src: (n: number) => string,\n caption?: (n: number) => string\n): GalleryImage[] {\n return Array.from({ length: count }, (_, i) => {\n const n = i + 1;\n return { src: src(n), caption: caption?.(n) };\n });\n}\n","<ul class=\"cc-gallery\" [style.background-image]=\"backgroundImage() ? 'url(' + backgroundImage() + ')' : null\">\n @for (image of images(); track image.src; let i = $index) {\n <li>\n <button type=\"button\" class=\"cc-gallery__item\" [attr.aria-label]=\"openLabel() + ': ' + (image.alt ?? image.caption ?? i + 1)\" (click)=\"open.set(i)\">\n <img [src]=\"image.thumb ?? image.src\" [alt]=\"image.alt ?? image.caption ?? ''\" loading=\"lazy\" decoding=\"async\" />\n </button>\n </li>\n }\n</ul>\n<cc-lightbox [images]=\"images()\" [(index)]=\"open\" />\n","/*\n * Public API Surface of @c-code/c-code-fw/ui\n */\n\n// Models\nexport * from './lib/models/plan.models';\nexport * from './lib/models/ui.models';\n\n// Utilities\nexport * from './lib/utils/text.utils';\nexport * from './lib/utils/plan.utils';\n\n// Services\nexport * from './lib/services/plan-catalog.service';\nexport * from './lib/services/seo.service';\n\n// Components\nexport * from './lib/components/whatsapp-button/whatsapp-button.component';\nexport * from './lib/components/promo-modal/promo-modal.component';\nexport * from './lib/components/page-banner/page-banner.component';\nexport * from './lib/components/info-item/info-item.component';\nexport * from './lib/components/faq/faq.component';\nexport * from './lib/components/social-links/social-links.component';\nexport * from './lib/components/plan-card/plan-card.component';\nexport * from './lib/components/category-menu/category-menu.component';\nexport * from './lib/components/search-box/search-box.component';\nexport * from './lib/components/plan-filter-form/plan-filter-form.component';\nexport * from './lib/components/plan-catalog/plan-catalog.component';\nexport * from './lib/components/plan-details/plan-details.component';\nexport * from './lib/components/lightbox/lightbox.component';\nexport * from './lib/components/gallery/gallery.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@c-code/c-code-fw",
3
- "version": "1.2.6",
3
+ "version": "1.3.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^19.2.0",
6
- "@angular/core": "^19.2.0"
6
+ "@angular/core": "^19.2.0",
7
+ "@angular/platform-browser": "^19.2.0",
8
+ "@angular/router": "^19.2.0",
9
+ "rxjs": "^7.8.0"
7
10
  },
8
11
  "dependencies": {
9
12
  "tslib": "^2.3.0"
@@ -18,6 +21,10 @@
18
21
  ".": {
19
22
  "types": "./index.d.ts",
20
23
  "default": "./fesm2022/c-code-c-code-fw.mjs"
24
+ },
25
+ "./ui": {
26
+ "types": "./ui/index.d.ts",
27
+ "default": "./fesm2022/c-code-c-code-fw-ui.mjs"
21
28
  }
22
29
  }
23
30
  }
package/ui/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="@c-code/c-code-fw/ui" />
5
+ export * from './public-api';
@@ -0,0 +1,19 @@
1
+ import { CategoryOption } from '../../models/plan.models';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Vertical category list with a single selected option.
5
+ * Replaces the `appElementActive` markup the sites used for plan categories.
6
+ *
7
+ * ```html
8
+ * <cc-category-menu [options]="categories" [(selected)]="category" />
9
+ * ```
10
+ */
11
+ export declare class CategoryMenuComponent<T = unknown> {
12
+ readonly options: import("@angular/core").InputSignal<CategoryOption<T>[]>;
13
+ /** Selected value. Use `[(selected)]` to bind both ways. */
14
+ readonly selected: import("@angular/core").ModelSignal<T | null>;
15
+ readonly title: import("@angular/core").InputSignal<string>;
16
+ select(value: T): void;
17
+ static ɵfac: i0.ɵɵFactoryDeclaration<CategoryMenuComponent<any>, never>;
18
+ static ɵcmp: i0.ɵɵComponentDeclaration<CategoryMenuComponent<any>, "cc-category-menu", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "selected": { "alias": "selected"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, { "selected": "selectedChange"; }, never, never, true, never>;
19
+ }
@@ -0,0 +1,16 @@
1
+ import { FaqItem } from '../../models/ui.models';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Frequently asked questions as native `<details>` elements, so the answers
5
+ * stay in the prerendered HTML for search engines.
6
+ *
7
+ * ```html
8
+ * <cc-faq title="PREGUNTAS FRECUENTES" [items]="faq" />
9
+ * ```
10
+ */
11
+ export declare class FaqComponent {
12
+ readonly items: import("@angular/core").InputSignal<FaqItem[]>;
13
+ readonly title: import("@angular/core").InputSignal<string>;
14
+ static ɵfac: i0.ɵɵFactoryDeclaration<FaqComponent, never>;
15
+ static ɵcmp: i0.ɵɵComponentDeclaration<FaqComponent, "cc-faq", never, { "items": { "alias": "items"; "required": true; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
16
+ }
@@ -0,0 +1,27 @@
1
+ import { GalleryImage } from '../../models/ui.models';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Thumbnail grid that opens a lightbox on click.
5
+ *
6
+ * ```html
7
+ * <cc-gallery [images]="images" />
8
+ * ```
9
+ *
10
+ * `numberedImages()` builds the list for folders named 1.jpeg, 2.jpeg, …
11
+ */
12
+ export declare class GalleryComponent {
13
+ readonly images: import("@angular/core").InputSignal<GalleryImage[]>;
14
+ readonly backgroundImage: import("@angular/core").InputSignal<string>;
15
+ readonly openLabel: import("@angular/core").InputSignal<string>;
16
+ protected readonly open: import("@angular/core").WritableSignal<number | null>;
17
+ static ɵfac: i0.ɵɵFactoryDeclaration<GalleryComponent, never>;
18
+ static ɵcmp: i0.ɵɵComponentDeclaration<GalleryComponent, "cc-gallery", never, { "images": { "alias": "images"; "required": true; "isSignal": true; }; "backgroundImage": { "alias": "backgroundImage"; "required": false; "isSignal": true; }; "openLabel": { "alias": "openLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
19
+ }
20
+ /**
21
+ * Builds a gallery list for numbered files.
22
+ *
23
+ * ```ts
24
+ * numberedImages(18, (i) => `assets/images/galery/${i}.jpeg`, (i) => `Instalaciones - foto ${i}`)
25
+ * ```
26
+ */
27
+ export declare function numberedImages(count: number, src: (n: number) => string, caption?: (n: number) => string): GalleryImage[];
@@ -0,0 +1,18 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * Title with a paragraph, used for policy sections.
4
+ * Extra content (lists, notes) can be projected under the text.
5
+ *
6
+ * ```html
7
+ * <cc-info-item title="Pagos" text="Aceptamos Nequi, Daviplata y efectivo." />
8
+ * <cc-info-item title="Reservaciones" [text]="texto">
9
+ * <ul><li>…</li></ul>
10
+ * </cc-info-item>
11
+ * ```
12
+ */
13
+ export declare class InfoItemComponent {
14
+ readonly title: import("@angular/core").InputSignal<string>;
15
+ readonly text: import("@angular/core").InputSignal<string>;
16
+ static ɵfac: i0.ɵɵFactoryDeclaration<InfoItemComponent, never>;
17
+ static ɵcmp: i0.ɵɵComponentDeclaration<InfoItemComponent, "cc-info-item", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "text": { "alias": "text"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
18
+ }
@@ -0,0 +1,26 @@
1
+ import { GalleryImage } from '../../models/ui.models';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Full-screen image viewer. It is open while `index` is not null.
5
+ * Arrow keys move between images and Escape closes it.
6
+ *
7
+ * ```html
8
+ * <cc-lightbox [images]="images" [(index)]="openIndex" />
9
+ * ```
10
+ */
11
+ export declare class LightboxComponent {
12
+ readonly images: import("@angular/core").InputSignal<GalleryImage[]>;
13
+ /** Index of the open image, or null when closed. Use `[(index)]`. */
14
+ readonly index: import("@angular/core").ModelSignal<number | null>;
15
+ readonly closeLabel: import("@angular/core").InputSignal<string>;
16
+ readonly prevLabel: import("@angular/core").InputSignal<string>;
17
+ readonly nextLabel: import("@angular/core").InputSignal<string>;
18
+ protected readonly current: import("@angular/core").Signal<GalleryImage | null>;
19
+ private document;
20
+ constructor();
21
+ close(): void;
22
+ next(): void;
23
+ prev(): void;
24
+ static ɵfac: i0.ɵɵFactoryDeclaration<LightboxComponent, never>;
25
+ static ɵcmp: i0.ɵɵComponentDeclaration<LightboxComponent, "cc-lightbox", never, { "images": { "alias": "images"; "required": true; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; "prevLabel": { "alias": "prevLabel"; "required": false; "isSignal": true; }; "nextLabel": { "alias": "nextLabel"; "required": false; "isSignal": true; }; }, { "index": "indexChange"; }, never, never, true, never>;
26
+ }
@@ -0,0 +1,23 @@
1
+ import { LinkTarget } from '../../models/ui.models';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Title strip at the top of a page, with an optional back link and icon.
5
+ * Renders the page's `<h1>`.
6
+ *
7
+ * ```html
8
+ * <cc-page-banner title="Reserva tu plan - Pareja" />
9
+ * <cc-page-banner [title]="plan.name" backLink="/planes" iconSrc="assets/icons/hoja.png" variant="plain" />
10
+ * ```
11
+ */
12
+ export declare class PageBannerComponent {
13
+ readonly title: import("@angular/core").InputSignal<string>;
14
+ /** `band` uses the surface color; `plain` uses the page background. */
15
+ readonly variant: import("@angular/core").InputSignal<"band" | "plain">;
16
+ readonly iconSrc: import("@angular/core").InputSignal<string>;
17
+ /** Shows a back arrow that links here. */
18
+ readonly backLink: import("@angular/core").InputSignal<LinkTarget | null>;
19
+ readonly backIconSrc: import("@angular/core").InputSignal<string>;
20
+ readonly backLabel: import("@angular/core").InputSignal<string>;
21
+ static ɵfac: i0.ɵɵFactoryDeclaration<PageBannerComponent, never>;
22
+ static ɵcmp: i0.ɵɵComponentDeclaration<PageBannerComponent, "cc-page-banner", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "backLink": { "alias": "backLink"; "required": false; "isSignal": true; }; "backIconSrc": { "alias": "backIconSrc"; "required": false; "isSignal": true; }; "backLabel": { "alias": "backLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
23
+ }
@@ -0,0 +1,23 @@
1
+ import { LinkTarget } from '../../models/ui.models';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Plan card: image, name and a button.
5
+ * With `link` the button navigates with the router; without it, it emits `ctaClick`.
6
+ *
7
+ * ```html
8
+ * <cc-plan-card [name]="plan.name" [imageSrc]="plan.imgPath" [link]="['/planes', slug]" />
9
+ * ```
10
+ */
11
+ export declare class PlanCardComponent {
12
+ readonly name: import("@angular/core").InputSignal<string>;
13
+ readonly imageSrc: import("@angular/core").InputSignal<string>;
14
+ /** Defaults to `name`. */
15
+ readonly imageAlt: import("@angular/core").InputSignal<string>;
16
+ /** Optional line under the name, for example the formatted price. */
17
+ readonly subtitle: import("@angular/core").InputSignal<string>;
18
+ readonly link: import("@angular/core").InputSignal<LinkTarget | null>;
19
+ readonly ctaLabel: import("@angular/core").InputSignal<string>;
20
+ readonly ctaClick: import("@angular/core").OutputEmitterRef<void>;
21
+ static ɵfac: i0.ɵɵFactoryDeclaration<PlanCardComponent, never>;
22
+ static ɵcmp: i0.ɵɵComponentDeclaration<PlanCardComponent, "cc-plan-card", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "imageSrc": { "alias": "imageSrc"; "required": true; "isSignal": true; }; "imageAlt": { "alias": "imageAlt"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "link": { "alias": "link"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; }, { "ctaClick": "ctaClick"; }, never, never, true, never>;
23
+ }
@@ -0,0 +1,78 @@
1
+ import { TemplateRef } from '@angular/core';
2
+ import { AdditionalService, CategoryOption, Plan, PlanCategory, PlanFilter, PriceRange } from '../../models/plan.models';
3
+ import { LinkTarget } from '../../models/ui.models';
4
+ import * as i0 from "@angular/core";
5
+ /** Context of a custom card template: `<ng-template ccPlanCard let-plan let-link="link">`. */
6
+ export interface PlanCardContext {
7
+ $implicit: Plan;
8
+ link: LinkTarget;
9
+ }
10
+ /** Marks an `<ng-template>` inside `cc-plan-catalog` as the card to render for each plan. */
11
+ export declare class PlanCardTemplateDirective {
12
+ static ngTemplateContextGuard(_dir: PlanCardTemplateDirective, ctx: unknown): ctx is PlanCardContext;
13
+ static ɵfac: i0.ɵɵFactoryDeclaration<PlanCardTemplateDirective, never>;
14
+ static ɵdir: i0.ɵɵDirectiveDeclaration<PlanCardTemplateDirective, "ng-template[ccPlanCard]", never, {}, {}, never, never, true, never>;
15
+ }
16
+ /**
17
+ * Full plan list page: title, name search, category menu, filter and card grid.
18
+ * It only needs the plans; the rest has Spanish defaults you can override.
19
+ *
20
+ * Selecting a category shows its plans. Searching by name or applying the filter
21
+ * looks across every category, as the sites already did.
22
+ *
23
+ * ```html
24
+ * <cc-plan-catalog
25
+ * [plans]="plans()"
26
+ * [services]="services()"
27
+ * [priceRanges]="ranges()"
28
+ * imageAltSuffix=" - Laurel Spa Medellín"
29
+ * />
30
+ * ```
31
+ */
32
+ export declare class PlanCatalogComponent {
33
+ readonly plans: import("@angular/core").InputSignal<Plan[]>;
34
+ readonly categories: import("@angular/core").InputSignal<CategoryOption<number>[]>;
35
+ readonly initialCategory: import("@angular/core").InputSignal<number>;
36
+ /** Shows how many plans each category has, counted from `plans`. */
37
+ readonly showCounts: import("@angular/core").InputSignal<boolean>;
38
+ /** Options for the filter form. The filter is hidden when both are empty. */
39
+ readonly services: import("@angular/core").InputSignal<AdditionalService[]>;
40
+ readonly priceRanges: import("@angular/core").InputSignal<PriceRange[]>;
41
+ /** Where each card links to. Defaults to `/planes/<slug>`. */
42
+ readonly detailsLink: import("@angular/core").InputSignal<(plan: Plan) => LinkTarget>;
43
+ readonly showBanner: import("@angular/core").InputSignal<boolean>;
44
+ readonly showSearch: import("@angular/core").InputSignal<boolean>;
45
+ readonly titlePrefix: import("@angular/core").InputSignal<string>;
46
+ readonly resultsTitle: import("@angular/core").InputSignal<string>;
47
+ readonly categoriesTitle: import("@angular/core").InputSignal<string>;
48
+ readonly searchPlaceholder: import("@angular/core").InputSignal<string>;
49
+ readonly searchIconSrc: import("@angular/core").InputSignal<string>;
50
+ readonly filterTitle: import("@angular/core").InputSignal<string>;
51
+ readonly filterIconSrc: import("@angular/core").InputSignal<string>;
52
+ readonly ctaLabel: import("@angular/core").InputSignal<string>;
53
+ /** Added after the plan name in each image's alt text. */
54
+ readonly imageAltSuffix: import("@angular/core").InputSignal<string>;
55
+ readonly emptyMessage: import("@angular/core").InputSignal<string>;
56
+ readonly categoryChange: import("@angular/core").OutputEmitterRef<number>;
57
+ protected readonly cardTemplate: import("@angular/core").Signal<TemplateRef<any> | undefined>;
58
+ protected readonly category: import("@angular/core").WritableSignal<number | null>;
59
+ protected readonly query: import("@angular/core").WritableSignal<string>;
60
+ protected readonly filter: import("@angular/core").WritableSignal<PlanFilter | null>;
61
+ protected readonly filterOpen: import("@angular/core").WritableSignal<boolean>;
62
+ /** Set when the user searched or filtered; results then cover every category. */
63
+ protected readonly searching: import("@angular/core").WritableSignal<boolean>;
64
+ /** Category that was open before searching, to return to it when the search is cleared. */
65
+ private lastCategory;
66
+ constructor();
67
+ protected readonly categoryOptions: import("@angular/core").Signal<CategoryOption<number>[]>;
68
+ protected readonly hasFilter: import("@angular/core").Signal<boolean>;
69
+ protected readonly visiblePlans: import("@angular/core").Signal<Plan[]>;
70
+ protected readonly title: import("@angular/core").Signal<string>;
71
+ protected selectCategory(value: PlanCategory | number | null): void;
72
+ protected onSearch(query: string): void;
73
+ protected onFilter(filter: PlanFilter): void;
74
+ protected linkFor(plan: Plan): LinkTarget;
75
+ private startSearch;
76
+ static ɵfac: i0.ɵɵFactoryDeclaration<PlanCatalogComponent, never>;
77
+ static ɵcmp: i0.ɵɵComponentDeclaration<PlanCatalogComponent, "cc-plan-catalog", never, { "plans": { "alias": "plans"; "required": true; "isSignal": true; }; "categories": { "alias": "categories"; "required": false; "isSignal": true; }; "initialCategory": { "alias": "initialCategory"; "required": false; "isSignal": true; }; "showCounts": { "alias": "showCounts"; "required": false; "isSignal": true; }; "services": { "alias": "services"; "required": false; "isSignal": true; }; "priceRanges": { "alias": "priceRanges"; "required": false; "isSignal": true; }; "detailsLink": { "alias": "detailsLink"; "required": false; "isSignal": true; }; "showBanner": { "alias": "showBanner"; "required": false; "isSignal": true; }; "showSearch": { "alias": "showSearch"; "required": false; "isSignal": true; }; "titlePrefix": { "alias": "titlePrefix"; "required": false; "isSignal": true; }; "resultsTitle": { "alias": "resultsTitle"; "required": false; "isSignal": true; }; "categoriesTitle": { "alias": "categoriesTitle"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "searchIconSrc": { "alias": "searchIconSrc"; "required": false; "isSignal": true; }; "filterTitle": { "alias": "filterTitle"; "required": false; "isSignal": true; }; "filterIconSrc": { "alias": "filterIconSrc"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; "imageAltSuffix": { "alias": "imageAltSuffix"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; }, { "categoryChange": "categoryChange"; }, ["cardTemplate"], never, true, never>;
78
+ }
@@ -0,0 +1,44 @@
1
+ import { Plan } from '../../models/plan.models';
2
+ import { LinkTarget } from '../../models/ui.models';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Plan detail view: title with a back link, photo, price, description, duration,
6
+ * number of people, included services, and a gift box with the booking button.
7
+ * It only renders; the page loads the plan and handles SEO.
8
+ *
9
+ * ```html
10
+ * <cc-plan-details [plan]="plan" [bookingUrl]="whatsappLink" imageAltSuffix=" en Laurel Spa Medellín" />
11
+ * ```
12
+ */
13
+ export declare class PlanDetailsComponent {
14
+ readonly plan: import("@angular/core").InputSignal<Plan>;
15
+ /** Link of the booking button, usually WhatsApp. */
16
+ readonly bookingUrl: import("@angular/core").InputSignal<string>;
17
+ readonly bookingLabel: import("@angular/core").InputSignal<string>;
18
+ readonly showBanner: import("@angular/core").InputSignal<boolean>;
19
+ readonly backLink: import("@angular/core").InputSignal<LinkTarget | null>;
20
+ readonly backLabel: import("@angular/core").InputSignal<string>;
21
+ readonly backIconSrc: import("@angular/core").InputSignal<string>;
22
+ readonly titleIconSrc: import("@angular/core").InputSignal<string>;
23
+ /** Added after the plan name in the photo's alt text. */
24
+ readonly imageAltSuffix: import("@angular/core").InputSignal<string>;
25
+ /** Arguments of Angular's `currency` pipe. */
26
+ readonly currencyCode: import("@angular/core").InputSignal<string>;
27
+ readonly currencyDisplay: import("@angular/core").InputSignal<string>;
28
+ readonly currencyDigits: import("@angular/core").InputSignal<string>;
29
+ readonly durationIconSrc: import("@angular/core").InputSignal<string>;
30
+ readonly personIconSrc: import("@angular/core").InputSignal<string>;
31
+ readonly peopleIconSrc: import("@angular/core").InputSignal<string>;
32
+ readonly personLabel: import("@angular/core").InputSignal<string>;
33
+ readonly peopleLabel: import("@angular/core").InputSignal<string>;
34
+ readonly includesLabel: import("@angular/core").InputSignal<string>;
35
+ /** Gift box. It is hidden when `perks` is empty. */
36
+ readonly perksTitle: import("@angular/core").InputSignal<string>;
37
+ readonly perks: import("@angular/core").InputSignal<string[]>;
38
+ readonly perksImageSrc: import("@angular/core").InputSignal<string>;
39
+ readonly perksImageAlt: import("@angular/core").InputSignal<string>;
40
+ protected readonly people: import("@angular/core").Signal<number>;
41
+ protected readonly services: import("@angular/core").Signal<import("@c-code/c-code-fw/ui").AdditionalService[]>;
42
+ static ɵfac: i0.ɵɵFactoryDeclaration<PlanDetailsComponent, never>;
43
+ static ɵcmp: i0.ɵɵComponentDeclaration<PlanDetailsComponent, "cc-plan-details", never, { "plan": { "alias": "plan"; "required": true; "isSignal": true; }; "bookingUrl": { "alias": "bookingUrl"; "required": true; "isSignal": true; }; "bookingLabel": { "alias": "bookingLabel"; "required": false; "isSignal": true; }; "showBanner": { "alias": "showBanner"; "required": false; "isSignal": true; }; "backLink": { "alias": "backLink"; "required": false; "isSignal": true; }; "backLabel": { "alias": "backLabel"; "required": false; "isSignal": true; }; "backIconSrc": { "alias": "backIconSrc"; "required": false; "isSignal": true; }; "titleIconSrc": { "alias": "titleIconSrc"; "required": false; "isSignal": true; }; "imageAltSuffix": { "alias": "imageAltSuffix"; "required": false; "isSignal": true; }; "currencyCode": { "alias": "currencyCode"; "required": false; "isSignal": true; }; "currencyDisplay": { "alias": "currencyDisplay"; "required": false; "isSignal": true; }; "currencyDigits": { "alias": "currencyDigits"; "required": false; "isSignal": true; }; "durationIconSrc": { "alias": "durationIconSrc"; "required": false; "isSignal": true; }; "personIconSrc": { "alias": "personIconSrc"; "required": false; "isSignal": true; }; "peopleIconSrc": { "alias": "peopleIconSrc"; "required": false; "isSignal": true; }; "personLabel": { "alias": "personLabel"; "required": false; "isSignal": true; }; "peopleLabel": { "alias": "peopleLabel"; "required": false; "isSignal": true; }; "includesLabel": { "alias": "includesLabel"; "required": false; "isSignal": true; }; "perksTitle": { "alias": "perksTitle"; "required": false; "isSignal": true; }; "perks": { "alias": "perks"; "required": false; "isSignal": true; }; "perksImageSrc": { "alias": "perksImageSrc"; "required": false; "isSignal": true; }; "perksImageAlt": { "alias": "perksImageAlt"; "required": false; "isSignal": true; }; }, {}, never, ["[ccDetailsMedia]", "*"], true, never>;
44
+ }
@@ -0,0 +1,28 @@
1
+ import { AdditionalService, PlanFilter, PriceRange } from '../../models/plan.models';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Filter by included service and price range. The options come in as inputs,
5
+ * so the form does not load data by itself.
6
+ *
7
+ * ```html
8
+ * <cc-plan-filter-form [services]="services" [priceRanges]="ranges" (filterChange)="apply($event)" />
9
+ * ```
10
+ */
11
+ export declare class PlanFilterFormComponent {
12
+ readonly services: import("@angular/core").InputSignal<AdditionalService[]>;
13
+ readonly priceRanges: import("@angular/core").InputSignal<PriceRange[]>;
14
+ readonly serviceLabel: import("@angular/core").InputSignal<string>;
15
+ readonly priceLabel: import("@angular/core").InputSignal<string>;
16
+ readonly anyLabel: import("@angular/core").InputSignal<string>;
17
+ readonly submitLabel: import("@angular/core").InputSignal<string>;
18
+ readonly resetLabel: import("@angular/core").InputSignal<string>;
19
+ /** Emits on submit, and with an empty filter on reset. */
20
+ readonly filterChange: import("@angular/core").OutputEmitterRef<PlanFilter>;
21
+ protected serviceIndex: import("@angular/core").WritableSignal<number>;
22
+ protected rangeIndex: import("@angular/core").WritableSignal<number>;
23
+ protected toIndex(event: Event): number;
24
+ submit(): void;
25
+ reset(): void;
26
+ static ɵfac: i0.ɵɵFactoryDeclaration<PlanFilterFormComponent, never>;
27
+ static ɵcmp: i0.ɵɵComponentDeclaration<PlanFilterFormComponent, "cc-plan-filter-form", never, { "services": { "alias": "services"; "required": false; "isSignal": true; }; "priceRanges": { "alias": "priceRanges"; "required": false; "isSignal": true; }; "serviceLabel": { "alias": "serviceLabel"; "required": false; "isSignal": true; }; "priceLabel": { "alias": "priceLabel"; "required": false; "isSignal": true; }; "anyLabel": { "alias": "anyLabel"; "required": false; "isSignal": true; }; "submitLabel": { "alias": "submitLabel"; "required": false; "isSignal": true; }; "resetLabel": { "alias": "resetLabel"; "required": false; "isSignal": true; }; }, { "filterChange": "filterChange"; }, never, never, true, never>;
28
+ }