@c-code/c-code-fw 1.3.0 → 2.1.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 (28) hide show
  1. package/README.md +118 -104
  2. package/fesm2022/c-code-c-code-fw-ui.mjs +622 -141
  3. package/fesm2022/c-code-c-code-fw-ui.mjs.map +1 -1
  4. package/package.json +1 -1
  5. package/ui/lib/components/button/button.component.d.ts +24 -0
  6. package/ui/lib/components/category-menu/category-menu.component.d.ts +12 -3
  7. package/ui/lib/components/faq/faq.component.d.ts +11 -4
  8. package/ui/lib/components/faq-item/faq-item.component.d.ts +21 -0
  9. package/ui/lib/components/gallery/gallery.component.d.ts +7 -3
  10. package/ui/lib/components/info-item/info-item.component.d.ts +10 -2
  11. package/ui/lib/components/lightbox/lightbox.component.d.ts +18 -3
  12. package/ui/lib/components/notice/notice.component.d.ts +17 -0
  13. package/ui/lib/components/page-banner/page-banner.component.d.ts +9 -3
  14. package/ui/lib/components/plan-card/plan-card.component.d.ts +29 -6
  15. package/ui/lib/components/plan-catalog/plan-catalog.component.d.ts +43 -19
  16. package/ui/lib/components/plan-details/plan-details.component.d.ts +26 -11
  17. package/ui/lib/components/plan-filter-form/plan-filter-form.component.d.ts +15 -5
  18. package/ui/lib/components/promo-modal/promo-modal.component.d.ts +39 -7
  19. package/ui/lib/components/search-box/search-box.component.d.ts +6 -1
  20. package/ui/lib/components/section-heading/section-heading.component.d.ts +34 -0
  21. package/ui/lib/components/social-links/social-links.component.d.ts +10 -6
  22. package/ui/lib/components/whatsapp-button/whatsapp-button.component.d.ts +12 -2
  23. package/ui/lib/models/promo.models.d.ts +25 -0
  24. package/ui/lib/utils/promo.utils.d.ts +18 -0
  25. package/ui/lib/utils/text.utils.d.ts +10 -0
  26. package/ui/public-api.d.ts +6 -0
  27. package/ui/theme/component-base.css +136 -0
  28. package/ui/theme/tokens.css +111 -45
@@ -1,24 +1,56 @@
1
+ import { ButtonVariant } from '../button/button.component';
1
2
  import * as i0 from "@angular/core";
2
3
  /**
3
- * Modal over the page, for example a promotion shown when the site opens.
4
- * Shows `imageSrc`, or the projected content when there is no image.
5
- * Closes with the ✕ button, a backdrop click or the Escape key.
4
+ * Modal over the page, for example a promotion. Shows `imageSrc`, or the projected
5
+ * content when there is no image, plus an optional call to action.
6
+ *
7
+ * It decides by itself when to open:
8
+ * - `autoOpen` opens it in the browser after `delayMs` (never during SSR, so it is not
9
+ * part of the prerendered page).
10
+ * - `rememberKey` stores the dismissal in localStorage for `rememberDays` days, so it
11
+ * does not reopen on every visit.
12
+ *
13
+ * Closes with the ✕ button, a backdrop click or Escape. Focus moves into the modal,
14
+ * stays inside while open and returns to the previous element on close.
6
15
  *
7
16
  * ```html
8
- * <cc-promo-modal [(open)]="showPromo" imageSrc="assets/images/pop.jpeg" imageAlt="Promoción" />
17
+ * <cc-promo-modal imageSrc="assets/images/pop.jpeg" imageAlt="Promoción" [delayMs]="6000"
18
+ * rememberKey="promo-octubre" ctaLabel="Reservar la promo" [ctaHref]="whatsapp" />
9
19
  * ```
20
+ *
21
+ * Tokens: --cc-modal-backdrop, --cc-modal-radius, --cc-modal-close-bg, --cc-modal-close-color.
10
22
  */
11
23
  export declare class PromoModalComponent {
12
- /** Whether the modal is visible. Use `[(open)]` to bind both ways. */
24
+ /** Whether the modal is visible. Use `[(open)]` to control it from outside. */
13
25
  readonly open: import("@angular/core").ModelSignal<boolean>;
26
+ /** Opens the modal by itself in the browser after `delayMs`. */
27
+ readonly autoOpen: import("@angular/core").InputSignal<boolean>;
28
+ readonly delayMs: import("@angular/core").InputSignal<number>;
29
+ /** localStorage key to remember the dismissal. Change it for each new promotion. */
30
+ readonly rememberKey: import("@angular/core").InputSignal<string>;
31
+ readonly rememberDays: import("@angular/core").InputSignal<number>;
14
32
  readonly imageSrc: import("@angular/core").InputSignal<string>;
15
33
  readonly imageAlt: import("@angular/core").InputSignal<string>;
16
- /** Makes the image a link, for example to WhatsApp. */
34
+ /** Makes the image a link. */
17
35
  readonly link: import("@angular/core").InputSignal<string>;
36
+ /** Button under the image. Hidden when `ctaHref` is empty. */
37
+ readonly ctaLabel: import("@angular/core").InputSignal<string>;
38
+ readonly ctaHref: import("@angular/core").InputSignal<string>;
39
+ readonly ctaVariant: import("@angular/core").InputSignal<ButtonVariant>;
18
40
  readonly closeOnBackdrop: import("@angular/core").InputSignal<boolean>;
19
41
  readonly closeLabel: import("@angular/core").InputSignal<string>;
20
42
  readonly closed: import("@angular/core").OutputEmitterRef<void>;
43
+ private readonly document;
44
+ private readonly box;
45
+ private readonly closeButton;
46
+ private readonly isOpen;
47
+ constructor();
21
48
  close(): void;
49
+ /** Keeps Tab and Shift+Tab inside the modal. */
50
+ protected trapFocus(event: KeyboardEvent): void;
51
+ private storage;
52
+ private wasDismissed;
53
+ private remember;
22
54
  static ɵfac: i0.ɵɵFactoryDeclaration<PromoModalComponent, never>;
23
- static ɵcmp: i0.ɵɵComponentDeclaration<PromoModalComponent, "cc-promo-modal", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "imageSrc": { "alias": "imageSrc"; "required": false; "isSignal": true; }; "imageAlt": { "alias": "imageAlt"; "required": false; "isSignal": true; }; "link": { "alias": "link"; "required": false; "isSignal": true; }; "closeOnBackdrop": { "alias": "closeOnBackdrop"; "required": false; "isSignal": true; }; "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "closed": "closed"; }, never, ["*"], true, never>;
55
+ static ɵcmp: i0.ɵɵComponentDeclaration<PromoModalComponent, "cc-promo-modal", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "autoOpen": { "alias": "autoOpen"; "required": false; "isSignal": true; }; "delayMs": { "alias": "delayMs"; "required": false; "isSignal": true; }; "rememberKey": { "alias": "rememberKey"; "required": false; "isSignal": true; }; "rememberDays": { "alias": "rememberDays"; "required": false; "isSignal": true; }; "imageSrc": { "alias": "imageSrc"; "required": false; "isSignal": true; }; "imageAlt": { "alias": "imageAlt"; "required": false; "isSignal": true; }; "link": { "alias": "link"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; "ctaHref": { "alias": "ctaHref"; "required": false; "isSignal": true; }; "ctaVariant": { "alias": "ctaVariant"; "required": false; "isSignal": true; }; "closeOnBackdrop": { "alias": "closeOnBackdrop"; "required": false; "isSignal": true; }; "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "closed": "closed"; }, never, ["*"], true, never>;
24
56
  }
@@ -5,14 +5,19 @@ import * as i0 from "@angular/core";
5
5
  * ```html
6
6
  * <cc-search-box placeholder="Buscar plan" [(value)]="query" (search)="onSearch($event)" />
7
7
  * ```
8
+ *
9
+ * Tokens: --cc-search-bg, --cc-search-border, --cc-search-btn-bg, --cc-search-btn-color,
10
+ * --cc-search-radius.
8
11
  */
9
12
  export declare class SearchBoxComponent {
10
13
  readonly value: import("@angular/core").ModelSignal<string>;
11
14
  readonly placeholder: import("@angular/core").InputSignal<string>;
15
+ /** Accessible name of the field. Defaults to `placeholder`. */
16
+ readonly label: import("@angular/core").InputSignal<string>;
12
17
  readonly buttonLabel: import("@angular/core").InputSignal<string>;
13
18
  readonly iconSrc: import("@angular/core").InputSignal<string>;
14
19
  readonly search: import("@angular/core").OutputEmitterRef<string>;
15
20
  onInput(event: Event): void;
16
21
  static ɵfac: i0.ɵɵFactoryDeclaration<SearchBoxComponent, never>;
17
- static ɵcmp: i0.ɵɵComponentDeclaration<SearchBoxComponent, "cc-search-box", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "buttonLabel": { "alias": "buttonLabel"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; }, never, never, true, never>;
22
+ static ɵcmp: i0.ɵɵComponentDeclaration<SearchBoxComponent, "cc-search-box", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "buttonLabel": { "alias": "buttonLabel"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; }, never, never, true, never>;
18
23
  }
@@ -0,0 +1,34 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * Section title with optional eyebrow, icon, subtitle and decorative rule.
4
+ * Use one per page section so every page shares the same heading rhythm.
5
+ *
6
+ * ```html
7
+ * <cc-section-heading title="Planes" subtitle="Para ti, en pareja o en grupo" iconSrc="assets/images/trebol.png" />
8
+ * <cc-section-heading title="Contacto" tone="inverse" align="center" level="h3" />
9
+ * ```
10
+ *
11
+ * Tokens: --cc-section-heading-color, --cc-section-heading-size, --cc-section-heading-rule,
12
+ * --cc-section-heading-gap, --cc-section-heading-icon-size.
13
+ */
14
+ export declare class SectionHeadingComponent {
15
+ readonly title: import("@angular/core").InputSignal<string>;
16
+ readonly subtitle: import("@angular/core").InputSignal<string>;
17
+ /** Small text above the title. */
18
+ readonly eyebrow: import("@angular/core").InputSignal<string>;
19
+ readonly iconSrc: import("@angular/core").InputSignal<string>;
20
+ /** Heading level. The visual size does not change with the level. */
21
+ readonly level: import("@angular/core").InputSignal<"h1" | "h2" | "h3">;
22
+ readonly align: import("@angular/core").InputSignal<"center" | "start">;
23
+ /** `default` for light backgrounds, `inverse` for dark ones, `accent` for brand-colored titles. */
24
+ readonly tone: import("@angular/core").InputSignal<"inverse" | "default" | "accent">;
25
+ /** Shows a short accent line under the title. */
26
+ readonly rule: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
27
+ /** `sm` for sub-sections, `md` (default) for page sections, `lg` for heroes. */
28
+ readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg">;
29
+ readonly italic: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
30
+ /** Id of the heading element, for anchors. */
31
+ readonly headingId: import("@angular/core").InputSignal<string | null>;
32
+ static ɵfac: i0.ɵɵFactoryDeclaration<SectionHeadingComponent, never>;
33
+ static ɵcmp: i0.ɵɵComponentDeclaration<SectionHeadingComponent, "cc-section-heading", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "eyebrow": { "alias": "eyebrow"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "level": { "alias": "level"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "rule": { "alias": "rule"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "italic": { "alias": "italic"; "required": false; "isSignal": true; }; "headingId": { "alias": "headingId"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
34
+ }
@@ -1,17 +1,21 @@
1
1
  import { SocialLink } from '../../models/ui.models';
2
2
  import * as i0 from "@angular/core";
3
3
  /**
4
- * Row of social network icons that open in a new tab.
4
+ * Row of social network icons that open in a new tab. Each link has a 44px tap area
5
+ * even when the icon is smaller.
5
6
  *
6
7
  * ```html
7
- * <cc-social-links [links]="socials" size="1.25rem" />
8
+ * <cc-social-links [links]="socials" size="sm" tone="inverse" />
8
9
  * ```
10
+ *
11
+ * Tokens: --cc-social-icon-size, --cc-social-gap.
9
12
  */
10
13
  export declare class SocialLinksComponent {
11
14
  readonly links: import("@angular/core").InputSignal<SocialLink[]>;
12
- /** Icon width, any CSS length. */
13
- readonly size: import("@angular/core").InputSignal<string>;
14
- readonly gap: import("@angular/core").InputSignal<string>;
15
+ /** Icon size: `sm` 1rem, `md` 1.5rem, `lg` 2.5rem. */
16
+ readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg">;
17
+ /** `inverse` for dark backgrounds: changes the hover and focus colors. */
18
+ readonly tone: import("@angular/core").InputSignal<"inverse" | "default">;
15
19
  static ɵfac: i0.ɵɵFactoryDeclaration<SocialLinksComponent, never>;
16
- static ɵcmp: i0.ɵɵComponentDeclaration<SocialLinksComponent, "cc-social-links", never, { "links": { "alias": "links"; "required": true; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "gap": { "alias": "gap"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
20
+ static ɵcmp: i0.ɵɵComponentDeclaration<SocialLinksComponent, "cc-social-links", never, { "links": { "alias": "links"; "required": true; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
17
21
  }
@@ -2,18 +2,28 @@ import * as i0 from "@angular/core";
2
2
  /**
3
3
  * Floating WhatsApp button, fixed to a corner of the screen.
4
4
  *
5
+ * `variant`:
6
+ * - `icon` (default): round button with the logo.
7
+ * - `extended`: pill with the logo and `label` as visible text.
8
+ *
5
9
  * ```html
6
10
  * <cc-whatsapp-button [href]="whatsappLink" />
7
- * <cc-whatsapp-button [href]="whatsappLink" iconSrc="assets/icons/whatsapp.png" position="left" />
11
+ * <cc-whatsapp-button [href]="whatsappLink" variant="extended" label="Reservar" position="left" />
8
12
  * ```
13
+ *
14
+ * Tokens: --cc-whatsapp, --cc-whatsapp-hover, --cc-whatsapp-size, --cc-whatsapp-offset-x,
15
+ * --cc-whatsapp-offset-y.
9
16
  */
10
17
  export declare class WhatsappButtonComponent {
11
18
  /** WhatsApp link. Build it with `whatsappUrl(phone, message)`. */
12
19
  readonly href: import("@angular/core").InputSignal<string>;
13
20
  /** Custom icon. Without it, the WhatsApp logo is drawn as SVG. */
14
21
  readonly iconSrc: import("@angular/core").InputSignal<string>;
22
+ /** Accessible name, and the visible text in the `extended` variant. */
15
23
  readonly label: import("@angular/core").InputSignal<string>;
24
+ readonly variant: import("@angular/core").InputSignal<"icon" | "extended">;
25
+ readonly size: import("@angular/core").InputSignal<"sm" | "md">;
16
26
  readonly position: import("@angular/core").InputSignal<"right" | "left">;
17
27
  static ɵfac: i0.ɵɵFactoryDeclaration<WhatsappButtonComponent, never>;
18
- static ɵcmp: i0.ɵɵComponentDeclaration<WhatsappButtonComponent, "cc-whatsapp-button", never, { "href": { "alias": "href"; "required": true; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
28
+ static ɵcmp: i0.ɵɵComponentDeclaration<WhatsappButtonComponent, "cc-whatsapp-button", never, { "href": { "alias": "href"; "required": true; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
19
29
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The site's promotional popup, as the CMS stores it (`promo.json`). The site passes it to
3
+ * `cc-promo-modal`; `promoState()` and `promoRememberKey()` decide when it shows.
4
+ */
5
+ export interface PromoConfig {
6
+ /** Off hides the popup whatever the dates say. */
7
+ active: boolean;
8
+ imageSrc: string;
9
+ /** Describes the image for screen readers and search engines. */
10
+ imageAlt: string;
11
+ /** Call to action. Empty hides the button. */
12
+ ctaLabel: string;
13
+ /** WhatsApp message the button opens. Empty makes the button open the general chat. */
14
+ message: string;
15
+ /** Seconds after the page loads before the popup opens. */
16
+ delaySeconds: number;
17
+ /** Days before it shows again to someone who closed it. 0 shows it on every visit. */
18
+ repeatDays: number;
19
+ /** First day it shows (YYYY-MM-DD, site's local time). Empty: from now on. */
20
+ startDate: string;
21
+ /** Last day it shows, included (YYYY-MM-DD). Empty: no end. */
22
+ endDate: string;
23
+ }
24
+ /** Where a promotion is in its life, for the CMS and for the site. */
25
+ export type PromoState = 'off' | 'scheduled' | 'running' | 'ended' | 'incomplete';
@@ -0,0 +1,18 @@
1
+ import { PromoConfig, PromoState } from '../models/promo.models';
2
+ /** Today's date as YYYY-MM-DD in the local time zone. */
3
+ export declare function localDate(now?: Date): string;
4
+ /**
5
+ * Where the promotion is on a given day:
6
+ * - `off`: switched off.
7
+ * - `incomplete`: on, but without an image.
8
+ * - `scheduled`: before `startDate`.
9
+ * - `ended`: after `endDate`.
10
+ * - `running`: shows.
11
+ */
12
+ export declare function promoState(promo: PromoConfig | null | undefined, now?: Date): PromoState;
13
+ export declare function isPromoRunning(promo: PromoConfig | null | undefined, now?: Date): boolean;
14
+ /**
15
+ * localStorage key that remembers who closed the popup. It changes when the image or the
16
+ * dates change, so a new promotion shows again to people who closed the previous one.
17
+ */
18
+ export declare function promoRememberKey(promo: PromoConfig): string;
@@ -13,3 +13,13 @@ export declare function truncateText(text: string, max?: number): string;
13
13
  * @param message Pre-filled message, optional.
14
14
  */
15
15
  export declare function whatsappUrl(phone: string, message?: string): string;
16
+ export interface PriceFormat {
17
+ /** BCP 47 locale. Defaults to "es-CO", which prints 259900 as "$ 259.900". */
18
+ locale?: string;
19
+ /** ISO 4217 code. Defaults to "COP". */
20
+ currency?: string;
21
+ /** Decimals to show. Defaults to 0. */
22
+ digits?: number;
23
+ }
24
+ /** Formats a price with `Intl.NumberFormat`: formatPrice(259900) -> "$ 259.900". */
25
+ export declare function formatPrice(value: number, { locale, currency, digits }?: PriceFormat): string;
@@ -1,9 +1,15 @@
1
1
  export * from './lib/models/plan.models';
2
2
  export * from './lib/models/ui.models';
3
+ export * from './lib/models/promo.models';
3
4
  export * from './lib/utils/text.utils';
4
5
  export * from './lib/utils/plan.utils';
6
+ export * from './lib/utils/promo.utils';
5
7
  export * from './lib/services/plan-catalog.service';
6
8
  export * from './lib/services/seo.service';
9
+ export * from './lib/components/button/button.component';
10
+ export * from './lib/components/section-heading/section-heading.component';
11
+ export * from './lib/components/notice/notice.component';
12
+ export * from './lib/components/faq-item/faq-item.component';
7
13
  export * from './lib/components/whatsapp-button/whatsapp-button.component';
8
14
  export * from './lib/components/promo-modal/promo-modal.component';
9
15
  export * from './lib/components/page-banner/page-banner.component';
@@ -0,0 +1,136 @@
1
+ /*
2
+ * Shared base for every @c-code/c-code-fw/ui component. Each component lists this file
3
+ * first in `styleUrls`, so the rules below are scoped to that component.
4
+ *
5
+ * The library does not own any brand. It reads a contract of semantic roles (--cc-*)
6
+ * that each site defines from its own palette and fonts, and resolves them into private
7
+ * --_* variables on the component host. Without a site theme, components render in
8
+ * neutral grays. Resolution happens on each host, so a role set on any ancestor element
9
+ * re-themes the components inside it.
10
+ *
11
+ * Structure (spacing, type scale, radii, shadows, motion) does belong to the library:
12
+ * those tokens have library defaults and components expose props to pick from them.
13
+ */
14
+ :host {
15
+ /* ---- Color roles: defined by the site ---- */
16
+ --_canvas: var(--cc-canvas, #ffffff); /* page background behind components */
17
+ --_surface: var(--cc-surface, #f5f5f4); /* cards, panels */
18
+ --_surface-alt: var(--cc-surface-alt, #fafaf9); /* alternative band, image placeholders */
19
+ --_text: var(--cc-text, #292524);
20
+ --_text-muted: var(--cc-text-muted, color-mix(in srgb, var(--_text) 72%, transparent));
21
+ --_heading: var(--cc-heading, #1c1917);
22
+ --_accent: var(--cc-accent, #1c1917); /* primary buttons, active items */
23
+ --_accent-hover: var(--cc-accent-hover, #44403c);
24
+ --_accent-text: var(--cc-accent-text, var(--_heading)); /* accent used as text, ≥4.5:1 */
25
+ --_on-accent: var(--cc-on-accent, #ffffff);
26
+ --_inverse: var(--cc-inverse, #1c1917); /* dark blocks: price badge, dark buttons */
27
+ --_inverse-hover: var(--cc-inverse-hover, #000000);
28
+ --_on-inverse: var(--cc-on-inverse, #ffffff);
29
+ --_border: var(--cc-border, #d6d3d1);
30
+ --_border-strong: var(--cc-border-strong, #78716c); /* inputs, ≥3:1 on white */
31
+ --_focus-ring: var(--cc-focus-ring, var(--_heading));
32
+ --_overlay: var(--cc-overlay, rgb(0 0 0 / 0.7));
33
+ --_danger: var(--cc-danger, #b91c1c);
34
+ --_danger-surface: var(--cc-danger-surface, #fef2f2);
35
+ --_on-danger-surface: var(--cc-on-danger-surface, #7f1d1d);
36
+ --_whatsapp: var(--cc-whatsapp, #1faf38);
37
+ --_whatsapp-hover: var(--cc-whatsapp-hover, #17902d);
38
+ --_on-whatsapp: var(--cc-on-whatsapp, #ffffff);
39
+
40
+ /* ---- Typography roles: defined by the site ---- */
41
+ --_font-body: var(--cc-font-body, inherit);
42
+ --_font-heading: var(--cc-font-heading, inherit);
43
+ --_heading-style: var(--cc-heading-style, normal); /* normal | italic */
44
+ --_heading-transform: var(--cc-heading-transform, none); /* none | uppercase */
45
+ --_heading-tracking: var(--cc-heading-tracking, normal);
46
+
47
+ /* ---- Structure: owned by the library ---- */
48
+ --_text-xs: var(--cc-text-xs, 0.75rem);
49
+ --_text-sm: var(--cc-text-sm, 0.875rem);
50
+ --_text-md: var(--cc-text-md, 1rem);
51
+ --_text-lg: var(--cc-text-lg, 1.125rem);
52
+ --_text-xl: var(--cc-text-xl, 1.25rem);
53
+ --_text-2xl: var(--cc-text-2xl, 1.5rem);
54
+ --_text-3xl: var(--cc-text-3xl, clamp(1.625rem, 1.2rem + 1.6vw, 2rem));
55
+ --_text-4xl: var(--cc-text-4xl, clamp(2rem, 1.4rem + 2.4vw, 2.75rem));
56
+ --_leading-tight: var(--cc-leading-tight, 1.2);
57
+ --_leading-snug: var(--cc-leading-snug, 1.4);
58
+ --_leading-body: var(--cc-leading-body, 1.6);
59
+ --_weight-regular: var(--cc-weight-regular, 400);
60
+ --_weight-medium: var(--cc-weight-medium, 500);
61
+ --_weight-semibold: var(--cc-weight-semibold, 600);
62
+ --_weight-bold: var(--cc-weight-bold, 700);
63
+ --_tracking-wide: var(--cc-tracking-wide, 0.04em);
64
+
65
+ --_space-1: var(--cc-space-1, 0.25rem);
66
+ --_space-2: var(--cc-space-2, 0.5rem);
67
+ --_space-3: var(--cc-space-3, 0.75rem);
68
+ --_space-4: var(--cc-space-4, 1rem);
69
+ --_space-5: var(--cc-space-5, 1.25rem);
70
+ --_space-6: var(--cc-space-6, 1.5rem);
71
+ --_space-8: var(--cc-space-8, 2rem);
72
+ --_space-10: var(--cc-space-10, 2.5rem);
73
+ --_space-12: var(--cc-space-12, 3rem);
74
+ --_space-16: var(--cc-space-16, 4rem);
75
+ --_space-20: var(--cc-space-20, 5rem);
76
+ --_space-24: var(--cc-space-24, 6rem);
77
+
78
+ --_radius: var(--cc-radius, 0);
79
+ --_radius-sm: var(--cc-radius-sm, 0.125rem);
80
+ --_radius-md: var(--cc-radius-md, 0.5rem);
81
+ --_radius-lg: var(--cc-radius-lg, 1rem);
82
+ --_radius-pill: var(--cc-radius-pill, 9999px);
83
+ --_shadow-sm: var(--cc-shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05));
84
+ --_shadow: var(--cc-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));
85
+ --_shadow-lg: var(--cc-shadow-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1));
86
+ --_shadow-xl: var(--cc-shadow-xl, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));
87
+
88
+ --_focus-width: var(--cc-focus-width, 2px);
89
+ --_focus-offset: var(--cc-focus-offset, 2px);
90
+ --_control-height: var(--cc-control-height, 2.75rem);
91
+ --_control-height-sm: var(--cc-control-height-sm, 2.25rem);
92
+ --_control-height-lg: var(--cc-control-height-lg, 3.25rem);
93
+ --_duration-fast: var(--cc-duration-fast, 150ms);
94
+ --_duration: var(--cc-duration, 250ms);
95
+ --_ease: var(--cc-ease, cubic-bezier(0.2, 0, 0, 1));
96
+ --_z-float: var(--cc-z-float, 20);
97
+ --_z-sticky: var(--cc-z-sticky, 30);
98
+ --_z-overlay: var(--cc-z-overlay, 50);
99
+
100
+ box-sizing: border-box;
101
+ font-family: var(--_font-body);
102
+ }
103
+
104
+ *,
105
+ *::before,
106
+ *::after {
107
+ box-sizing: border-box;
108
+ }
109
+
110
+ :focus-visible {
111
+ outline: var(--_focus-width) solid var(--_focus-ring);
112
+ outline-offset: var(--_focus-offset);
113
+ }
114
+
115
+ /* Visually hidden text for screen readers. */
116
+ .cc-sr-only {
117
+ position: absolute;
118
+ width: 1px;
119
+ height: 1px;
120
+ padding: 0;
121
+ margin: -1px;
122
+ overflow: hidden;
123
+ clip: rect(0, 0, 0, 0);
124
+ white-space: nowrap;
125
+ border: 0;
126
+ }
127
+
128
+ @media (prefers-reduced-motion: reduce) {
129
+ *,
130
+ *::before,
131
+ *::after {
132
+ animation-duration: 0.01ms !important;
133
+ animation-iteration-count: 1 !important;
134
+ transition-duration: 0.01ms !important;
135
+ }
136
+ }
@@ -1,45 +1,111 @@
1
- /*
2
- * Design tokens for @c-code/c-code-fw/ui.
3
- *
4
- * The components already fall back to these values, so this file is optional.
5
- * To apply a site's palette, set the variables in its global styles:
6
- *
7
- * :root {
8
- * --cc-primary: #4C6B4A;
9
- * --cc-secondary: #CEAB5D;
10
- * }
11
- *
12
- * The overrides work on any element, not just :root, so a section can use its own palette.
13
- *
14
- * ROLES (optional). By default each role follows a palette color. Set a role only
15
- * when a site needs it to differ from its palette:
16
- *
17
- * --cc-surface cards, sidebar, detail panel (default --cc-secondary-light)
18
- * --cc-text text inside components (default --cc-primary)
19
- * --cc-heading titles and the price badge (default --cc-primary)
20
- * --cc-accent buttons and highlighted text (default --cc-secondary)
21
- * --cc-accent-hover button hover (default --cc-secondary-dark)
22
- * --cc-on-accent text on buttons and badges (default --cc-bg)
23
- *
24
- * Each component also has its own variables (for example --cc-plan-card-bg).
25
- * They are listed in the README.
26
- */
27
- :root {
28
- /* Palette */
29
- --cc-primary: #4c6b4a;
30
- --cc-primary-light: #a0ba9e;
31
- --cc-primary-dark: #022b04;
32
- --cc-secondary: #ceab5d;
33
- --cc-secondary-light: #f8f4ee;
34
- --cc-secondary-dark: #9a7521;
35
- --cc-bg: #f0ffef;
36
-
37
- /* Typography */
38
- --cc-font-body: inherit;
39
- --cc-font-heading: inherit;
40
-
41
- /* Shape */
42
- --cc-radius: 0;
43
- --cc-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
44
- --cc-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
45
- }
1
+ /*
2
+ * Structural tokens of @c-code/c-code-fw/ui: type scale, spacing, radii, shadows,
3
+ * controls, motion and layers.
4
+ *
5
+ * Every component already falls back to these values, so this file is optional. Load it
6
+ * (angular.json → styles) when the site wants to use the same scales in its own markup,
7
+ * for example by mapping Tailwind's font sizes and spacing to var(--cc-text-*) and
8
+ * var(--cc-space-*). Override any of them to change the rhythm of a whole site.
9
+ *
10
+ * COLORS AND FONTS ARE NOT HERE. They belong to each site. The components read this
11
+ * contract of roles, which the site sets from its own palette (with any variable names):
12
+ *
13
+ * --cc-canvas page background behind components
14
+ * --cc-surface cards, panels, sidebar
15
+ * --cc-surface-alt alternative band, image placeholders
16
+ * --cc-text body text
17
+ * --cc-text-muted counters, metadata (default: --cc-text at 72%)
18
+ * --cc-heading titles
19
+ * --cc-accent primary buttons, active items
20
+ * --cc-accent-hover primary button hover
21
+ * --cc-accent-text accent used as text; needs 4.5:1 on --cc-canvas and --cc-surface
22
+ * --cc-on-accent text on --cc-accent; needs 4.5:1
23
+ * --cc-inverse dark blocks: price badge, dark buttons
24
+ * --cc-inverse-hover
25
+ * --cc-on-inverse text on --cc-inverse
26
+ * --cc-border dividers
27
+ * --cc-border-strong input borders, 3:1 on --cc-canvas
28
+ * --cc-focus-ring keyboard focus outline
29
+ * --cc-overlay modal and lightbox backdrop
30
+ * --cc-danger, --cc-danger-surface, --cc-on-danger-surface warnings
31
+ * --cc-whatsapp, --cc-whatsapp-hover WhatsApp buttons
32
+ * --cc-font-body, --cc-font-heading font families
33
+ * --cc-heading-style (normal|italic), --cc-heading-transform, --cc-heading-tracking
34
+ *
35
+ * Example, in the site's styles.css:
36
+ *
37
+ * :root {
38
+ * --laurel-green: #4c6b4a;
39
+ * --laurel-gold: #ceab5d;
40
+ * --cc-heading: var(--laurel-green);
41
+ * --cc-accent: var(--laurel-gold);
42
+ * --cc-on-accent: #022b04;
43
+ * }
44
+ *
45
+ * Without a site theme the components render in neutral grays.
46
+ *
47
+ * Breakpoints (media queries cannot use variables): sm 640px, md 768px, lg 1024px, xl 1280px.
48
+ */
49
+ :root {
50
+ /* Type scale */
51
+ --cc-text-xs: 0.75rem;
52
+ --cc-text-sm: 0.875rem;
53
+ --cc-text-md: 1rem;
54
+ --cc-text-lg: 1.125rem;
55
+ --cc-text-xl: 1.25rem;
56
+ --cc-text-2xl: 1.5rem;
57
+ --cc-text-3xl: clamp(1.625rem, 1.2rem + 1.6vw, 2rem);
58
+ --cc-text-4xl: clamp(2rem, 1.4rem + 2.4vw, 2.75rem);
59
+ --cc-leading-tight: 1.2;
60
+ --cc-leading-snug: 1.4;
61
+ --cc-leading-body: 1.6;
62
+ --cc-weight-regular: 400;
63
+ --cc-weight-medium: 500;
64
+ --cc-weight-semibold: 600;
65
+ --cc-weight-bold: 700;
66
+ --cc-tracking-wide: 0.04em;
67
+
68
+ /* Spacing (4px base, same steps as Tailwind) */
69
+ --cc-space-1: 0.25rem;
70
+ --cc-space-2: 0.5rem;
71
+ --cc-space-3: 0.75rem;
72
+ --cc-space-4: 1rem;
73
+ --cc-space-5: 1.25rem;
74
+ --cc-space-6: 1.5rem;
75
+ --cc-space-8: 2rem;
76
+ --cc-space-10: 2.5rem;
77
+ --cc-space-12: 3rem;
78
+ --cc-space-16: 4rem;
79
+ --cc-space-20: 5rem;
80
+ --cc-space-24: 6rem;
81
+
82
+ /* Shape */
83
+ --cc-radius: 0; /* surfaces: cards, panels */
84
+ --cc-radius-sm: 0.125rem;
85
+ --cc-radius-md: 0.5rem;
86
+ --cc-radius-lg: 1rem;
87
+ --cc-radius-pill: 9999px;
88
+ --cc-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
89
+ --cc-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
90
+ --cc-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
91
+ --cc-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
92
+
93
+ /* Focus */
94
+ --cc-focus-width: 2px;
95
+ --cc-focus-offset: 2px;
96
+
97
+ /* Controls */
98
+ --cc-control-height: 2.75rem; /* 44px minimum tap target */
99
+ --cc-control-height-sm: 2.25rem;
100
+ --cc-control-height-lg: 3.25rem;
101
+
102
+ /* Motion */
103
+ --cc-duration-fast: 150ms;
104
+ --cc-duration: 250ms;
105
+ --cc-ease: cubic-bezier(0.2, 0, 0, 1);
106
+
107
+ /* Layers */
108
+ --cc-z-float: 20;
109
+ --cc-z-sticky: 30;
110
+ --cc-z-overlay: 50;
111
+ }