@fluencypassdevs/cycle 1.13.0 → 1.15.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.
- package/bin/mcp.mjs +199 -0
- package/dist/chunk-5HPZCREI.js +55 -0
- package/dist/chunk-5HPZCREI.js.map +1 -0
- package/dist/chunk-6JH4YWWX.js +29 -0
- package/dist/chunk-6JH4YWWX.js.map +1 -0
- package/dist/chunk-7LH7PSQS.js +77 -0
- package/dist/chunk-7LH7PSQS.js.map +1 -0
- package/dist/chunk-EACBYVLK.js +29 -0
- package/dist/chunk-EACBYVLK.js.map +1 -0
- package/dist/chunk-HDCU6QSG.js +24 -0
- package/dist/chunk-HDCU6QSG.js.map +1 -0
- package/dist/chunk-K2NUBNSA.js +17 -0
- package/dist/chunk-K2NUBNSA.js.map +1 -0
- package/dist/chunk-NW56QDQM.js +64 -0
- package/dist/chunk-NW56QDQM.js.map +1 -0
- package/dist/chunk-NZXP7BAH.js +33 -0
- package/dist/chunk-NZXP7BAH.js.map +1 -0
- package/dist/chunk-TPRZMMT6.js +57 -0
- package/dist/chunk-TPRZMMT6.js.map +1 -0
- package/dist/chunk-UJ6OER6O.js +49 -0
- package/dist/chunk-UJ6OER6O.js.map +1 -0
- package/dist/chunk-VXWMC2NK.js +24 -0
- package/dist/chunk-VXWMC2NK.js.map +1 -0
- package/dist/chunk-VYHRNO77.js +27 -0
- package/dist/chunk-VYHRNO77.js.map +1 -0
- package/dist/chunk-XHKFKLHD.js +26 -0
- package/dist/chunk-XHKFKLHD.js.map +1 -0
- package/dist/chunk-XQUQDZVA.js +36 -0
- package/dist/chunk-XQUQDZVA.js.map +1 -0
- package/dist/hooks/use-intersection.d.ts +34 -0
- package/dist/hooks/use-intersection.js +4 -0
- package/dist/hooks/use-intersection.js.map +1 -0
- package/dist/hooks/use-reduced-motion.d.ts +11 -0
- package/dist/hooks/use-reduced-motion.js +4 -0
- package/dist/hooks/use-reduced-motion.js.map +1 -0
- package/dist/marketing/effects/card-dark-glow.d.ts +30 -0
- package/dist/marketing/effects/card-dark-glow.js +5 -0
- package/dist/marketing/effects/card-dark-glow.js.map +1 -0
- package/dist/marketing/effects/circle-glow.d.ts +26 -0
- package/dist/marketing/effects/circle-glow.js +5 -0
- package/dist/marketing/effects/circle-glow.js.map +1 -0
- package/dist/marketing/effects/index.d.ts +4 -0
- package/dist/marketing/effects/index.js +7 -0
- package/dist/marketing/effects/index.js.map +1 -0
- package/dist/marketing/effects/marquee.d.ts +30 -0
- package/dist/marketing/effects/marquee.js +5 -0
- package/dist/marketing/effects/marquee.js.map +1 -0
- package/dist/marketing/layout/index.d.ts +2 -0
- package/dist/marketing/layout/index.js +5 -0
- package/dist/marketing/layout/index.js.map +1 -0
- package/dist/marketing/layout/section.d.ts +36 -0
- package/dist/marketing/layout/section.js +5 -0
- package/dist/marketing/layout/section.js.map +1 -0
- package/dist/marketing/motion/animated-number.d.ts +35 -0
- package/dist/marketing/motion/animated-number.js +7 -0
- package/dist/marketing/motion/animated-number.js.map +1 -0
- package/dist/marketing/motion/index.d.ts +6 -0
- package/dist/marketing/motion/index.js +10 -0
- package/dist/marketing/motion/index.js.map +1 -0
- package/dist/marketing/motion/reveal-on-scroll.d.ts +27 -0
- package/dist/marketing/motion/reveal-on-scroll.js +7 -0
- package/dist/marketing/motion/reveal-on-scroll.js.map +1 -0
- package/dist/marketing/motion/smooth-anchor.d.ts +31 -0
- package/dist/marketing/motion/smooth-anchor.js +4 -0
- package/dist/marketing/motion/smooth-anchor.js.map +1 -0
- package/dist/marketing/motion/stagger-reveal.d.ts +30 -0
- package/dist/marketing/motion/stagger-reveal.js +7 -0
- package/dist/marketing/motion/stagger-reveal.js.map +1 -0
- package/dist/marketing/typography/hero-heading.d.ts +25 -0
- package/dist/marketing/typography/hero-heading.js +5 -0
- package/dist/marketing/typography/hero-heading.js.map +1 -0
- package/dist/marketing/typography/index.d.ts +5 -0
- package/dist/marketing/typography/index.js +8 -0
- package/dist/marketing/typography/index.js.map +1 -0
- package/dist/marketing/typography/section-eyebrow.d.ts +21 -0
- package/dist/marketing/typography/section-eyebrow.js +5 -0
- package/dist/marketing/typography/section-eyebrow.js.map +1 -0
- package/dist/marketing/typography/section-heading.d.ts +37 -0
- package/dist/marketing/typography/section-heading.js +5 -0
- package/dist/marketing/typography/section-heading.js.map +1 -0
- package/dist/marketing/typography/section-subtitle.d.ts +19 -0
- package/dist/marketing/typography/section-subtitle.js +5 -0
- package/dist/marketing/typography/section-subtitle.js.map +1 -0
- package/dist/styles/tokens.css +192 -0
- package/package.json +37 -1
package/bin/mcp.mjs
CHANGED
|
@@ -943,6 +943,205 @@ import { Home, Search } from "@fluencypassdevs/cycle/icons/lucide"
|
|
|
943
943
|
<CycleIcon icon={Search} size="md" aria-label="Buscar" />`,
|
|
944
944
|
keywords: ["icone", "icon", "lucide", "svg", "grafico"],
|
|
945
945
|
},
|
|
946
|
+
// ─── Marketing — Camada 1 (motion utilities) ────────────────────────────────
|
|
947
|
+
{
|
|
948
|
+
name: "RevealOnScroll",
|
|
949
|
+
import: `import { RevealOnScroll } from "@fluencypassdevs/cycle/marketing/motion/reveal-on-scroll"`,
|
|
950
|
+
description: "MARKETING (landing pages). Wrapper que aplica fade-up suave (translateY 8px + opacity 0->1, 720ms) quando o elemento entra no viewport. Usa IntersectionObserver. Respeita prefers-reduced-motion (entrega visivel sem transicao). Compoe com qualquer conteudo via children.",
|
|
951
|
+
props: [
|
|
952
|
+
{ name: "as", type: '"div" | "section" | "article" | "header" | "footer" | "main" | "aside"', default: '"div"' },
|
|
953
|
+
{ name: "delay", type: "number", default: "0", note: "delay extra antes da animacao (ms)" },
|
|
954
|
+
{ name: "threshold", type: "number", default: "0.15", note: "fracao do elemento visivel pra disparar (0-1)" },
|
|
955
|
+
{ name: "rootMargin", type: "string", default: '"0px 0px -10% 0px"', note: "margin do viewport (CSS shorthand)" },
|
|
956
|
+
{ name: "repeat", type: "boolean", default: "false", note: "reanima toda vez que reentrar no viewport" },
|
|
957
|
+
],
|
|
958
|
+
example: `<RevealOnScroll>
|
|
959
|
+
<h2>Aparece com fade-up</h2>
|
|
960
|
+
</RevealOnScroll>
|
|
961
|
+
|
|
962
|
+
<RevealOnScroll as="section" delay={200}>
|
|
963
|
+
<Card>Atrasa 200ms a entrada</Card>
|
|
964
|
+
</RevealOnScroll>`,
|
|
965
|
+
keywords: ["marketing", "landing", "motion", "animation", "scroll", "fade", "reveal", "entrada"],
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: "StaggerReveal",
|
|
969
|
+
import: `import { StaggerReveal } from "@fluencypassdevs/cycle/marketing/motion/stagger-reveal"`,
|
|
970
|
+
description: "MARKETING (landing pages). Container que anima seus filhos em sequencia (fade-up com delay incremental). Clona cada filho via React.Children.map e injeta --stagger-index como CSS var; animacao acontece via .stagger-child no globals.css. Util pra grids de cards, listas de features, secao de pilares. Respeita prefers-reduced-motion.",
|
|
971
|
+
props: [
|
|
972
|
+
{ name: "step", type: "number", default: "100", note: "delay entre cada filho (ms)" },
|
|
973
|
+
{ name: "threshold", type: "number", default: "0.15", note: "fracao do container visivel pra disparar" },
|
|
974
|
+
{ name: "rootMargin", type: "string", default: '"0px 0px -10% 0px"' },
|
|
975
|
+
],
|
|
976
|
+
example: `<StaggerReveal step={120} className="grid grid-cols-3 gap-3">
|
|
977
|
+
<Card>1</Card>
|
|
978
|
+
<Card>2</Card>
|
|
979
|
+
<Card>3</Card>
|
|
980
|
+
</StaggerReveal>`,
|
|
981
|
+
keywords: ["marketing", "landing", "motion", "animation", "stagger", "sequencia", "cards", "grid"],
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
name: "AnimatedNumber",
|
|
985
|
+
import: `import { AnimatedNumber } from "@fluencypassdevs/cycle/marketing/motion/animated-number"`,
|
|
986
|
+
description: "MARKETING (landing pages). Conta de `from` ate `value` com easing easeOutCubic ao entrar no viewport. Suporta prefix/suffix, decimals, locale (pt-BR ou plain). Aplica tabular-nums automaticamente pra largura estavel. Respeita prefers-reduced-motion (pula pro valor final).",
|
|
987
|
+
props: [
|
|
988
|
+
{ name: "value", type: "number", default: "-", note: "valor final (obrigatorio)" },
|
|
989
|
+
{ name: "from", type: "number", default: "0" },
|
|
990
|
+
{ name: "duration", type: "number", default: "1400", note: "duracao da animacao (ms)" },
|
|
991
|
+
{ name: "prefix", type: "string", default: '""', note: 'ex: "R$ ", "+"' },
|
|
992
|
+
{ name: "suffix", type: "string", default: '""', note: 'ex: "%", "k"' },
|
|
993
|
+
{ name: "decimals", type: "number", default: "0" },
|
|
994
|
+
{ name: "locale", type: '"pt-BR" | "plain"', default: '"pt-BR"' },
|
|
995
|
+
{ name: "threshold", type: "number", default: "0.4" },
|
|
996
|
+
],
|
|
997
|
+
example: `<AnimatedNumber value={1247} suffix="+" />
|
|
998
|
+
<AnimatedNumber prefix="R$ " value={3.5} decimals={1} suffix="M" />
|
|
999
|
+
<AnimatedNumber value={97} suffix="%" />`,
|
|
1000
|
+
keywords: ["marketing", "landing", "motion", "counter", "number", "stats", "kpi", "metric"],
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
name: "SmoothAnchor",
|
|
1004
|
+
import: `import { SmoothAnchor } from "@fluencypassdevs/cycle/marketing/motion/smooth-anchor"`,
|
|
1005
|
+
description: "MARKETING (landing pages). Intercepta cliques em links de hash (<a href=#X>) e faz scroll suave com easing easeInOutCubic em vez do scroll-behavior:smooth nativo. Monta UMA VEZ por app (no RootLayout). Renderiza null (so registra listener global). Pulsa destino apos chegar via .anchor-target-pulse. Respeita prefers-reduced-motion (scroll instantaneo).",
|
|
1006
|
+
props: [
|
|
1007
|
+
{ name: "headerOffset", type: "number", default: "96", note: "offset em px (compensar header sticky)" },
|
|
1008
|
+
{ name: "duration", type: "number", default: "900", note: "duracao do scroll (ms)" },
|
|
1009
|
+
],
|
|
1010
|
+
example: `// app/layout.tsx
|
|
1011
|
+
<body>
|
|
1012
|
+
<SmoothAnchor headerOffset={64} duration={900} />
|
|
1013
|
+
{children}
|
|
1014
|
+
</body>
|
|
1015
|
+
|
|
1016
|
+
// Qualquer link de hash agora usa scroll suave:
|
|
1017
|
+
<a href="#solucao">Ver solucao</a>
|
|
1018
|
+
<section id="solucao" data-anchor-pulse>...</section>`,
|
|
1019
|
+
keywords: ["marketing", "landing", "motion", "scroll", "anchor", "hash", "navegacao", "suave"],
|
|
1020
|
+
},
|
|
1021
|
+
// ─── Marketing — Camada 2a (Foundation visual: layout + typography + effects) ─
|
|
1022
|
+
{
|
|
1023
|
+
name: "Section",
|
|
1024
|
+
import: `import { Section } from "@fluencypassdevs/cycle/marketing/layout/section"`,
|
|
1025
|
+
description: "MARKETING (landing pages) — Camada 2a. Wrapper de dobra. Padroniza padding/altura via variant (hero=min-h-screen flex flex-col, default=py-20 md:py-28) e alterna fundo via tone (dark default, light aplica .light-surface). Generaliza o HomeSection do site corporativo. Pareie com SectionHeading/Eyebrow/Subtitle.",
|
|
1026
|
+
props: [
|
|
1027
|
+
{ name: "as", type: '"section" | "div"', default: '"section"' },
|
|
1028
|
+
{ name: "variant", type: '"hero" | "default"', default: '"default"', note: "hero ocupa min-h-screen; default tem py generoso" },
|
|
1029
|
+
{ name: "tone", type: '"dark" | "light"', default: '"dark"', note: "light aplica .light-surface (override de tokens semanticos)" },
|
|
1030
|
+
{ name: "id", type: "string", default: "-", note: "ID para ancoragem via SmoothAnchor" },
|
|
1031
|
+
],
|
|
1032
|
+
example: `<Section variant="hero">
|
|
1033
|
+
<HeroHeading>Acelere o ingles do seu time</HeroHeading>
|
|
1034
|
+
</Section>
|
|
1035
|
+
|
|
1036
|
+
<Section id="pilares" variant="default" tone="light">
|
|
1037
|
+
<SectionEyebrow align="center">Para empresas</SectionEyebrow>
|
|
1038
|
+
<SectionHeading>3 pilares essenciais</SectionHeading>
|
|
1039
|
+
<div className="grid md:grid-cols-3 gap-6">{cards}</div>
|
|
1040
|
+
</Section>`,
|
|
1041
|
+
keywords: ["marketing", "landing", "layout", "section", "dobra", "wrapper", "hero"],
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
name: "HeroHeading",
|
|
1045
|
+
import: `import { HeroHeading } from "@fluencypassdevs/cycle/marketing/typography/hero-heading"`,
|
|
1046
|
+
description: "MARKETING — Camada 2a. Titulo dramatico para hero (h1 default). Sizes grandes, font-bold, leading apertado (1.07), text-balance, max-w-2xl. Use no primeiro titulo da landing.",
|
|
1047
|
+
props: [
|
|
1048
|
+
{ name: "as", type: '"h1" | "h2" | "div"', default: '"h1"' },
|
|
1049
|
+
{ name: "align", type: '"left" | "center"', default: '"left"' },
|
|
1050
|
+
],
|
|
1051
|
+
example: `<HeroHeading>Acelere o ingles do seu time</HeroHeading>
|
|
1052
|
+
<HeroHeading align="center">Aprenda rapido com IA</HeroHeading>`,
|
|
1053
|
+
keywords: ["marketing", "landing", "typography", "heading", "h1", "hero", "titulo"],
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
name: "SectionHeading",
|
|
1057
|
+
import: `import { SectionHeading } from "@fluencypassdevs/cycle/marketing/typography/section-heading"`,
|
|
1058
|
+
description: "MARKETING — Camada 2a. Titulo de dobra (h2 default). Dois pesos: light (font-light elegante, default) e bold (font-bold impacto). leading 1.15, text-balance. Pareie com SectionEyebrow acima e SectionSubtitle abaixo.",
|
|
1059
|
+
props: [
|
|
1060
|
+
{ name: "as", type: '"h2" | "h3"', default: '"h2"' },
|
|
1061
|
+
{ name: "weight", type: '"light" | "bold"', default: '"light"' },
|
|
1062
|
+
{ name: "align", type: '"left" | "center"', default: '"center"' },
|
|
1063
|
+
{ name: "maxW", type: '"wide" | "prose"', default: '"wide"', note: "wide=max-w-5xl, prose=max-w-3xl" },
|
|
1064
|
+
],
|
|
1065
|
+
example: `<SectionHeading>
|
|
1066
|
+
Conteudo, professores e tecnologia no mesmo lugar
|
|
1067
|
+
</SectionHeading>
|
|
1068
|
+
|
|
1069
|
+
<SectionHeading weight="bold" align="left" maxW="prose">
|
|
1070
|
+
Mude o jeito de aprender ingles
|
|
1071
|
+
</SectionHeading>`,
|
|
1072
|
+
keywords: ["marketing", "landing", "typography", "heading", "h2", "section", "dobra"],
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: "SectionEyebrow",
|
|
1076
|
+
import: `import { SectionEyebrow } from "@fluencypassdevs/cycle/marketing/typography/section-eyebrow"`,
|
|
1077
|
+
description: "MARKETING — Camada 2a. Rotulo monospace acima do SectionHeading que categoriza a dobra. text-xs font-mono uppercase tracking-widest text-muted-foreground.",
|
|
1078
|
+
props: [
|
|
1079
|
+
{ name: "as", type: '"p" | "span" | "div"', default: '"p"' },
|
|
1080
|
+
{ name: "align", type: '"left" | "center"', default: '"left"' },
|
|
1081
|
+
],
|
|
1082
|
+
example: `<SectionEyebrow align="center">Para empresas</SectionEyebrow>
|
|
1083
|
+
<SectionHeading>Acelere o ingles do seu time</SectionHeading>`,
|
|
1084
|
+
keywords: ["marketing", "landing", "typography", "eyebrow", "label", "rotulo", "categoria"],
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
name: "SectionSubtitle",
|
|
1088
|
+
import: `import { SectionSubtitle } from "@fluencypassdevs/cycle/marketing/typography/section-subtitle"`,
|
|
1089
|
+
description: "MARKETING — Camada 2a. Body de apoio abaixo do SectionHeading. text-base sm:text-lg md:text-xl muted-foreground leading-relaxed text-balance max-w-3xl. align center (default) adiciona mx-auto.",
|
|
1090
|
+
props: [
|
|
1091
|
+
{ name: "align", type: '"left" | "center"', default: '"center"' },
|
|
1092
|
+
],
|
|
1093
|
+
example: `<SectionHeading>Nossa solucao em 3 passos</SectionHeading>
|
|
1094
|
+
<SectionSubtitle>
|
|
1095
|
+
Do diagnostico ao certificado, num caminho desenhado pra resultado.
|
|
1096
|
+
</SectionSubtitle>`,
|
|
1097
|
+
keywords: ["marketing", "landing", "typography", "subtitle", "subtitulo", "body", "lead"],
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
name: "CircleGlow",
|
|
1101
|
+
import: `import { CircleGlow } from "@fluencypassdevs/cycle/marketing/effects/circle-glow"`,
|
|
1102
|
+
description: "MARKETING — Camada 2a. Decoracao de elipse radial pulsante (4.5s scale + opacity) posicionada absolute. Container precisa ser relative. Sempre aria-hidden + pointer-events none. Respeita prefers-reduced-motion.",
|
|
1103
|
+
props: [
|
|
1104
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"lg"', note: "controla transform: scale()" },
|
|
1105
|
+
],
|
|
1106
|
+
example: `<div className="relative overflow-hidden">
|
|
1107
|
+
<CircleGlow className="-z-10" />
|
|
1108
|
+
<div className="grid grid-cols-3 gap-6">{cards}</div>
|
|
1109
|
+
</div>`,
|
|
1110
|
+
keywords: ["marketing", "landing", "effects", "glow", "decoration", "background", "rose"],
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
name: "CardDarkGlow",
|
|
1114
|
+
import: `import { CardDarkGlow } from "@fluencypassdevs/cycle/marketing/effects/card-dark-glow"`,
|
|
1115
|
+
description: "MARKETING — Camada 2a. Wrapper de card com inset highlight + glow rose subtil + hover lift opcional (translateY -3px). Prop interactive (default true) habilita o hover. Pareie com CircleGlow no container externo pro efeito 'dark premium'.",
|
|
1116
|
+
props: [
|
|
1117
|
+
{ name: "as", type: '"div" | "article" | "section"', default: '"div"' },
|
|
1118
|
+
{ name: "interactive", type: "boolean", default: "true", note: "habilita hover lift + glow stronger" },
|
|
1119
|
+
],
|
|
1120
|
+
example: `<CardDarkGlow className="rounded-2xl bg-card border p-6">
|
|
1121
|
+
<h3>Titulo</h3>
|
|
1122
|
+
<p>Descricao do card</p>
|
|
1123
|
+
</CardDarkGlow>
|
|
1124
|
+
|
|
1125
|
+
<CardDarkGlow interactive={false}>...sem hover</CardDarkGlow>`,
|
|
1126
|
+
keywords: ["marketing", "landing", "effects", "card", "shadow", "glow", "hover", "lift"],
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
name: "Marquee",
|
|
1130
|
+
import: `import { Marquee } from "@fluencypassdevs/cycle/marketing/effects/marquee"`,
|
|
1131
|
+
description: "MARKETING — Camada 2a. Scroll horizontal infinito (logo cloud, partner band). Duplica children pra loop seamless (segundo bloco aria-hidden). Pausa em hover e focus-within (a11y). Respeita prefers-reduced-motion.",
|
|
1132
|
+
props: [
|
|
1133
|
+
{ name: "speed", type: "number", default: "40", note: "duracao de um ciclo em segundos" },
|
|
1134
|
+
{ name: "direction", type: '"left" | "right"', default: '"left"' },
|
|
1135
|
+
{ name: "pauseOnHover", type: "boolean", default: "true" },
|
|
1136
|
+
{ name: "gap", type: "number", default: "48", note: "espacamento entre items em px" },
|
|
1137
|
+
],
|
|
1138
|
+
example: `<Marquee speed={30} gap={64}>
|
|
1139
|
+
<img src="/logos/logo-a.png" alt="Cliente A" />
|
|
1140
|
+
<img src="/logos/logo-b.png" alt="Cliente B" />
|
|
1141
|
+
<img src="/logos/logo-c.png" alt="Cliente C" />
|
|
1142
|
+
</Marquee>`,
|
|
1143
|
+
keywords: ["marketing", "landing", "effects", "marquee", "scroll", "horizontal", "logo", "cloud", "partner", "band"],
|
|
1144
|
+
},
|
|
946
1145
|
]
|
|
947
1146
|
|
|
948
1147
|
// ─── Design Tokens ─────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadProps, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var Marquee = React.forwardRef(function Marquee2(_a, ref) {
|
|
7
|
+
var _b = _a, {
|
|
8
|
+
speed = 40,
|
|
9
|
+
direction = "left",
|
|
10
|
+
pauseOnHover = true,
|
|
11
|
+
gap = 48,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
style
|
|
15
|
+
} = _b, props = __objRest(_b, [
|
|
16
|
+
"speed",
|
|
17
|
+
"direction",
|
|
18
|
+
"pauseOnHover",
|
|
19
|
+
"gap",
|
|
20
|
+
"className",
|
|
21
|
+
"children",
|
|
22
|
+
"style"
|
|
23
|
+
]);
|
|
24
|
+
const trackStyle = {
|
|
25
|
+
["--cycle-marquee-duration"]: `${speed}s`,
|
|
26
|
+
["--cycle-marquee-gap"]: `${gap}px`
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
"div",
|
|
30
|
+
__spreadProps(__spreadValues({
|
|
31
|
+
ref,
|
|
32
|
+
"data-slot": "marquee",
|
|
33
|
+
"data-pause-on-hover": pauseOnHover ? "true" : "false",
|
|
34
|
+
className: cn("cycle-marquee", className),
|
|
35
|
+
style
|
|
36
|
+
}, props), {
|
|
37
|
+
children: /* @__PURE__ */ jsxs(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
className: "cycle-marquee-track",
|
|
41
|
+
"data-direction": direction,
|
|
42
|
+
style: trackStyle,
|
|
43
|
+
children: [
|
|
44
|
+
children,
|
|
45
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "contents", children })
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export { Marquee };
|
|
54
|
+
//# sourceMappingURL=chunk-5HPZCREI.js.map
|
|
55
|
+
//# sourceMappingURL=chunk-5HPZCREI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/effects/marquee.tsx"],"names":["Marquee"],"mappings":";;;;;AA8BA,IAAM,OAAA,GAAgB,KAAA,CAAA,UAAA,CAAyC,SAASA,QAAAA,CACtE,IAUA,GAAA,EACA;AAXA,EAAA,IAAA,EAAA,GAAA,EAAA,EACE;AAAA,IAAA,KAAA,GAAQ,EAAA;AAAA,IACR,SAAA,GAAY,MAAA;AAAA,IACZ,YAAA,GAAe,IAAA;AAAA,IACf,GAAA,GAAM,EAAA;AAAA,IACN,SAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GAtCJ,GA+BE,EAAA,EAQK,KAAA,GAAA,SAAA,CARL,EAAA,EAQK;AAAA,IAPH,OAAA;AAAA,IACA,WAAA;AAAA,IACA,cAAA;AAAA,IACA,KAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAMF,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,CAAC,0BAAoC,GAAG,CAAA,EAAG,KAAK,CAAA,CAAA,CAAA;AAAA,IAChD,CAAC,qBAA+B,GAAG,CAAA,EAAG,GAAG,CAAA,EAAA;AAAA,GAC3C;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA,aAAA,CAAA,cAAA,CAAA;AAAA,MACC,GAAA;AAAA,MACA,WAAA,EAAU,SAAA;AAAA,MACV,qBAAA,EAAqB,eAAe,MAAA,GAAS,OAAA;AAAA,MAC7C,SAAA,EAAW,EAAA,CAAG,eAAA,EAAiB,SAAS,CAAA;AAAA,MACxC;AAAA,KAAA,EACI,KAAA,CAAA,EANL;AAAA,MAQC,QAAA,kBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,qBAAA;AAAA,UACV,gBAAA,EAAgB,SAAA;AAAA,UAChB,KAAA,EAAO,UAAA;AAAA,UAEN,QAAA,EAAA;AAAA,YAAA,QAAA;AAAA,gCAEA,KAAA,EAAA,EAAI,aAAA,EAAY,MAAA,EAAO,SAAA,EAAU,YAC/B,QAAA,EACH;AAAA;AAAA;AAAA;AACF,KAAA;AAAA,GACF;AAEJ,CAAC","file":"chunk-5HPZCREI.js","sourcesContent":["import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface MarqueeProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Duracao de um ciclo completo em segundos. Default `40`. */\n speed?: number\n /** Direcao do movimento. Default `\"left\"`. */\n direction?: \"left\" | \"right\"\n /** Pausa em `:hover` e `:focus-within`. Default `true` (acessibilidade). */\n pauseOnHover?: boolean\n /** Espacamento entre cada item em px. Default `48`. */\n gap?: number\n}\n\n/**\n * Scroll horizontal infinito (logo cloud, partner band). Duplica os children\n * internamente pra criar loop seamless. Pausa em hover/focus pra dar tempo\n * de leitura. Respeita `prefers-reduced-motion` (anima zerada).\n *\n * Importante: a duplicacao usa `aria-hidden` no segundo bloco pra evitar\n * leitura repetida por screen readers.\n *\n * @example\n * <Marquee speed={30} gap={64}>\n * <img src=\"/logos/logo-a.png\" alt=\"Cliente A\" />\n * <img src=\"/logos/logo-b.png\" alt=\"Cliente B\" />\n * <img src=\"/logos/logo-c.png\" alt=\"Cliente C\" />\n * </Marquee>\n */\nconst Marquee = React.forwardRef<HTMLDivElement, MarqueeProps>(function Marquee(\n {\n speed = 40,\n direction = \"left\",\n pauseOnHover = true,\n gap = 48,\n className,\n children,\n style,\n ...props\n },\n ref\n) {\n // Style merge: nossas CSS vars + qualquer style passado pelo consumer\n const trackStyle = {\n [\"--cycle-marquee-duration\" as string]: `${speed}s`,\n [\"--cycle-marquee-gap\" as string]: `${gap}px`,\n }\n\n return (\n <div\n ref={ref}\n data-slot=\"marquee\"\n data-pause-on-hover={pauseOnHover ? \"true\" : \"false\"}\n className={cn(\"cycle-marquee\", className)}\n style={style}\n {...props}\n >\n <div\n className=\"cycle-marquee-track\"\n data-direction={direction}\n style={trackStyle}\n >\n {children}\n {/* Duplicacao pro loop seamless — `aria-hidden` pra screen readers ignorarem. */}\n <div aria-hidden=\"true\" className=\"contents\">\n {children}\n </div>\n </div>\n </div>\n )\n})\n\nexport { Marquee }\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var SectionHeading = React.forwardRef(
|
|
7
|
+
function SectionHeading2(_a, ref) {
|
|
8
|
+
var _b = _a, { as = "h2", weight = "light", align = "center", maxW = "wide", className } = _b, props = __objRest(_b, ["as", "weight", "align", "maxW", "className"]);
|
|
9
|
+
const Tag = as;
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
Tag,
|
|
12
|
+
__spreadValues({
|
|
13
|
+
ref,
|
|
14
|
+
"data-slot": "section-heading",
|
|
15
|
+
className: cn(
|
|
16
|
+
"text-4xl sm:text-5xl md:text-6xl tracking-tight leading-[1.15] text-balance",
|
|
17
|
+
weight === "light" ? "font-light" : "font-bold",
|
|
18
|
+
maxW === "wide" ? "max-w-5xl" : "max-w-3xl",
|
|
19
|
+
align === "center" ? "text-center mx-auto" : "text-left",
|
|
20
|
+
className
|
|
21
|
+
)
|
|
22
|
+
}, props)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export { SectionHeading };
|
|
28
|
+
//# sourceMappingURL=chunk-6JH4YWWX.js.map
|
|
29
|
+
//# sourceMappingURL=chunk-6JH4YWWX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/typography/section-heading.tsx"],"names":["SectionHeading"],"mappings":";;;;;AAuCA,IAAM,cAAA,GAAuB,KAAA,CAAA,UAAA;AAAA,EAC3B,SAASA,eAAAA,CACP,EAAA,EACA,GAAA,EACA;AAFA,IAAA,IAAA,EAAA,GAAA,EAAA,EAAE,OAAK,IAAA,EAAM,MAAA,GAAS,OAAA,EAAS,KAAA,GAAQ,UAAU,IAAA,GAAO,MAAA,EAAQ,SAAA,EAzCpE,GAyCI,IAA8E,KAAA,GAAA,SAAA,CAA9E,EAAA,EAA8E,CAA5E,IAAA,EAAW,QAAA,EAAkB,SAAkB,MAAA,EAAe,WAAA,CAAA,CAAA;AAGhE,IAAA,MAAM,GAAA,GAAM,EAAA;AACZ,IAAA,uBACE,GAAA;AAAA,MAAC,GAAA;AAAA,MAAA,cAAA,CAAA;AAAA,QACC,GAAA;AAAA,QACA,WAAA,EAAU,iBAAA;AAAA,QACV,SAAA,EAAW,EAAA;AAAA,UACT,6EAAA;AAAA,UACA,MAAA,KAAW,UAAU,YAAA,GAAe,WAAA;AAAA,UACpC,IAAA,KAAS,SAAS,WAAA,GAAc,WAAA;AAAA,UAChC,KAAA,KAAU,WAAW,qBAAA,GAAwB,WAAA;AAAA,UAC7C;AAAA;AACF,OAAA,EACI,KAAA;AAAA,KACN;AAAA,EAEJ;AACF","file":"chunk-6JH4YWWX.js","sourcesContent":["import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype SectionHeadingTag = \"h2\" | \"h3\"\n\nexport interface SectionHeadingProps\n extends Omit<React.HTMLAttributes<HTMLHeadingElement>, \"color\"> {\n /** Tag renderizada. Default `\"h2\"`. */\n as?: SectionHeadingTag\n /**\n * Peso/intencao visual:\n * - `\"light\"` (default): `font-light` — elegante, usual em maioria das dobras.\n * - `\"bold\"`: `font-bold` — impacto, usado em momentos de virada (quebra).\n */\n weight?: \"light\" | \"bold\"\n /** Alinhamento. Default `\"center\"`. */\n align?: \"left\" | \"center\"\n /**\n * Largura maxima do bloco de texto:\n * - `\"wide\"` (default): `max-w-5xl` — usual centro/elegante.\n * - `\"prose\"`: `max-w-3xl` — quando o titulo e mais conciso.\n */\n maxW?: \"wide\" | \"prose\"\n}\n\n/**\n * Titulo de dobra de landing. Dois pesos principais (`light` elegante vs `bold`\n * impacto). Pareie com `SectionEyebrow` acima e `SectionSubtitle` abaixo.\n *\n * @example\n * <SectionHeading>\n * Conteudo, professores e <strong>tecnologia</strong> no mesmo lugar\n * </SectionHeading>\n *\n * <SectionHeading weight=\"bold\" align=\"left\" maxW=\"prose\">\n * Mude o jeito de aprender ingles\n * </SectionHeading>\n */\nconst SectionHeading = React.forwardRef<HTMLHeadingElement, SectionHeadingProps>(\n function SectionHeading(\n { as = \"h2\", weight = \"light\", align = \"center\", maxW = \"wide\", className, ...props },\n ref\n ) {\n const Tag = as as React.ElementType\n return (\n <Tag\n ref={ref}\n data-slot=\"section-heading\"\n className={cn(\n \"text-4xl sm:text-5xl md:text-6xl tracking-tight leading-[1.15] text-balance\",\n weight === \"light\" ? \"font-light\" : \"font-bold\",\n maxW === \"wide\" ? \"max-w-5xl\" : \"max-w-3xl\",\n align === \"center\" ? \"text-center mx-auto\" : \"text-left\",\n className\n )}\n {...props}\n />\n )\n }\n)\n\nexport { SectionHeading }\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useReducedMotion } from './chunk-K2NUBNSA.js';
|
|
2
|
+
import { useIntersection } from './chunk-XQUQDZVA.js';
|
|
3
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
4
|
+
import { __objRest, __spreadProps, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { jsxs } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function easeOutCubic(t) {
|
|
9
|
+
return 1 - Math.pow(1 - t, 3);
|
|
10
|
+
}
|
|
11
|
+
function formatNumber(n, decimals, locale) {
|
|
12
|
+
if (locale === "pt-BR") {
|
|
13
|
+
return n.toLocaleString("pt-BR", {
|
|
14
|
+
minimumFractionDigits: decimals,
|
|
15
|
+
maximumFractionDigits: decimals
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return n.toFixed(decimals);
|
|
19
|
+
}
|
|
20
|
+
function AnimatedNumber(_a) {
|
|
21
|
+
var _b = _a, {
|
|
22
|
+
value,
|
|
23
|
+
from = 0,
|
|
24
|
+
duration = 1400,
|
|
25
|
+
prefix = "",
|
|
26
|
+
suffix = "",
|
|
27
|
+
decimals = 0,
|
|
28
|
+
locale = "pt-BR",
|
|
29
|
+
threshold = 0.4,
|
|
30
|
+
className
|
|
31
|
+
} = _b, props = __objRest(_b, [
|
|
32
|
+
"value",
|
|
33
|
+
"from",
|
|
34
|
+
"duration",
|
|
35
|
+
"prefix",
|
|
36
|
+
"suffix",
|
|
37
|
+
"decimals",
|
|
38
|
+
"locale",
|
|
39
|
+
"threshold",
|
|
40
|
+
"className"
|
|
41
|
+
]);
|
|
42
|
+
const reduced = useReducedMotion();
|
|
43
|
+
const { ref, isVisible } = useIntersection({
|
|
44
|
+
threshold,
|
|
45
|
+
startVisible: reduced
|
|
46
|
+
});
|
|
47
|
+
const [current, setCurrent] = React.useState(reduced ? value : from);
|
|
48
|
+
const startedRef = React.useRef(false);
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
if (reduced) {
|
|
51
|
+
setCurrent(value);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!isVisible || startedRef.current) return;
|
|
55
|
+
startedRef.current = true;
|
|
56
|
+
const start = performance.now();
|
|
57
|
+
let frame = 0;
|
|
58
|
+
const tick = (now) => {
|
|
59
|
+
const elapsed = now - start;
|
|
60
|
+
const t = Math.min(elapsed / duration, 1);
|
|
61
|
+
const next = from + (value - from) * easeOutCubic(t);
|
|
62
|
+
setCurrent(next);
|
|
63
|
+
if (t < 1) frame = requestAnimationFrame(tick);
|
|
64
|
+
};
|
|
65
|
+
frame = requestAnimationFrame(tick);
|
|
66
|
+
return () => cancelAnimationFrame(frame);
|
|
67
|
+
}, [isVisible, value, from, duration, reduced]);
|
|
68
|
+
return /* @__PURE__ */ jsxs("span", __spreadProps(__spreadValues({ ref, className: cn("tabular-nums", className) }, props), { children: [
|
|
69
|
+
prefix,
|
|
70
|
+
formatNumber(current, decimals, locale),
|
|
71
|
+
suffix
|
|
72
|
+
] }));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { AnimatedNumber };
|
|
76
|
+
//# sourceMappingURL=chunk-7LH7PSQS.js.map
|
|
77
|
+
//# sourceMappingURL=chunk-7LH7PSQS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/motion/animated-number.tsx"],"names":[],"mappings":"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","file":"chunk-7LH7PSQS.js","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useIntersection } from \"@/hooks/use-intersection\"\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\"\n\nexport interface AnimatedNumberProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Valor final do contador. */\n value: number\n /** Valor inicial. Default `0`. */\n from?: number\n /** Duracao da animacao em ms. Default `1400`. */\n duration?: number\n /** Texto antes do numero (ex: `\"R$ \"` ou `\"+\"`). */\n prefix?: string\n /** Texto depois do numero (ex: `\"%\"` ou `\"k\"`). */\n suffix?: string\n /** Casas decimais. Default `0`. */\n decimals?: number\n /**\n * Formato dos numeros. `\"pt-BR\"` usa separador `.` e decimal `,`;\n * `\"plain\"` usa `toFixed`. Default `\"pt-BR\"`.\n */\n locale?: \"pt-BR\" | \"plain\"\n /** Threshold do IntersectionObserver. Default `0.4`. */\n threshold?: number\n}\n\nfunction easeOutCubic(t: number): number {\n return 1 - Math.pow(1 - t, 3)\n}\n\nfunction formatNumber(n: number, decimals: number, locale: \"pt-BR\" | \"plain\"): string {\n if (locale === \"pt-BR\") {\n return n.toLocaleString(\"pt-BR\", {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n })\n }\n return n.toFixed(decimals)\n}\n\n/**\n * Conta de `from` ate `value` com easing `easeOutCubic` quando o componente\n * entra no viewport. Aplica `tabular-nums` automaticamente pra largura estavel\n * durante a contagem (evita \"saltos\" no layout).\n *\n * Acessibilidade: respeita `prefers-reduced-motion` — pula direto pro valor\n * final, sem animar.\n */\nfunction AnimatedNumber({\n value,\n from = 0,\n duration = 1400,\n prefix = \"\",\n suffix = \"\",\n decimals = 0,\n locale = \"pt-BR\",\n threshold = 0.4,\n className,\n ...props\n}: AnimatedNumberProps) {\n const reduced = useReducedMotion()\n const { ref, isVisible } = useIntersection<HTMLSpanElement>({\n threshold,\n startVisible: reduced,\n })\n const [current, setCurrent] = React.useState(reduced ? value : from)\n const startedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (reduced) {\n setCurrent(value)\n return\n }\n if (!isVisible || startedRef.current) return\n startedRef.current = true\n\n const start = performance.now()\n let frame = 0\n const tick = (now: number) => {\n const elapsed = now - start\n const t = Math.min(elapsed / duration, 1)\n const next = from + (value - from) * easeOutCubic(t)\n setCurrent(next)\n if (t < 1) frame = requestAnimationFrame(tick)\n }\n frame = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(frame)\n }, [isVisible, value, from, duration, reduced])\n\n return (\n <span ref={ref} className={cn(\"tabular-nums\", className)} {...props}>\n {prefix}\n {formatNumber(current, decimals, locale)}\n {suffix}\n </span>\n )\n}\n\nexport { AnimatedNumber }\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var HeroHeading = React.forwardRef(
|
|
7
|
+
function HeroHeading2(_a, ref) {
|
|
8
|
+
var _b = _a, { as = "h1", align = "left", className } = _b, props = __objRest(_b, ["as", "align", "className"]);
|
|
9
|
+
const Tag = as;
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
Tag,
|
|
12
|
+
__spreadValues({
|
|
13
|
+
ref,
|
|
14
|
+
"data-slot": "hero-heading",
|
|
15
|
+
className: cn(
|
|
16
|
+
"text-3xl sm:text-4xl md:text-5xl lg:text-[56px]",
|
|
17
|
+
"font-bold leading-[1.07] tracking-tight text-balance",
|
|
18
|
+
"max-w-2xl",
|
|
19
|
+
align === "center" && "text-center mx-auto",
|
|
20
|
+
className
|
|
21
|
+
)
|
|
22
|
+
}, props)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export { HeroHeading };
|
|
28
|
+
//# sourceMappingURL=chunk-EACBYVLK.js.map
|
|
29
|
+
//# sourceMappingURL=chunk-EACBYVLK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/typography/hero-heading.tsx"],"names":["HeroHeading"],"mappings":";;;;;AA2BA,IAAM,WAAA,GAAoB,KAAA,CAAA,UAAA;AAAA,EACxB,SAASA,YAAAA,CAAY,EAAA,EAAoD,GAAA,EAAK;AAAzD,IAAA,IAAA,EAAA,GAAA,EAAA,EAAE,EAAA,EAAA,GAAK,IAAA,EAAM,KAAA,GAAQ,MAAA,EAAQ,SAAA,EA5BpD,GA4BuB,EAAA,EAA2C,KAAA,GAAA,SAAA,CAA3C,EAAA,EAA2C,CAAzC,IAAA,EAAW,OAAA,EAAgB,WAAA,CAAA,CAAA;AAChD,IAAA,MAAM,GAAA,GAAM,EAAA;AACZ,IAAA,uBACE,GAAA;AAAA,MAAC,GAAA;AAAA,MAAA,cAAA,CAAA;AAAA,QACC,GAAA;AAAA,QACA,WAAA,EAAU,cAAA;AAAA,QACV,SAAA,EAAW,EAAA;AAAA,UACT,iDAAA;AAAA,UACA,sDAAA;AAAA,UACA,WAAA;AAAA,UACA,UAAU,QAAA,IAAY,qBAAA;AAAA,UACtB;AAAA;AACF,OAAA,EACI,KAAA;AAAA,KACN;AAAA,EAEJ;AACF","file":"chunk-EACBYVLK.js","sourcesContent":["import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype HeroHeadingTag = \"h1\" | \"h2\" | \"div\"\n\nexport interface HeroHeadingProps\n extends Omit<React.HTMLAttributes<HTMLHeadingElement>, \"color\"> {\n /** Tag renderizada. Default `\"h1\"`. */\n as?: HeroHeadingTag\n /** Alinhamento horizontal. Default `\"left\"`. */\n align?: \"left\" | \"center\"\n}\n\n/**\n * Titulo dramatico de hero — sizes grandes, weight bold, leading apertado\n * (1.07) pra dar \"punch\" visual. Use no `<h1>` da primeira dobra de landings.\n *\n * @example\n * <HeroHeading>\n * Acelere o ingles do seu time\n * </HeroHeading>\n *\n * <HeroHeading align=\"center\">\n * Aprenda <RotatingWord words={[\"rapido\", \"facil\"]} /> com IA\n * </HeroHeading>\n */\nconst HeroHeading = React.forwardRef<HTMLHeadingElement, HeroHeadingProps>(\n function HeroHeading({ as = \"h1\", align = \"left\", className, ...props }, ref) {\n const Tag = as as React.ElementType\n return (\n <Tag\n ref={ref}\n data-slot=\"hero-heading\"\n className={cn(\n \"text-3xl sm:text-4xl md:text-5xl lg:text-[56px]\",\n \"font-bold leading-[1.07] tracking-tight text-balance\",\n \"max-w-2xl\",\n align === \"center\" && \"text-center mx-auto\",\n className\n )}\n {...props}\n />\n )\n }\n)\n\nexport { HeroHeading }\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var CircleGlow = React.forwardRef(
|
|
7
|
+
function CircleGlow2(_a, ref) {
|
|
8
|
+
var _b = _a, { size = "lg", className } = _b, props = __objRest(_b, ["size", "className"]);
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
"div",
|
|
11
|
+
__spreadValues({
|
|
12
|
+
ref,
|
|
13
|
+
"aria-hidden": "true",
|
|
14
|
+
"data-slot": "circle-glow",
|
|
15
|
+
"data-size": size,
|
|
16
|
+
className: cn("cycle-circle-glow", className)
|
|
17
|
+
}, props)
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export { CircleGlow };
|
|
23
|
+
//# sourceMappingURL=chunk-HDCU6QSG.js.map
|
|
24
|
+
//# sourceMappingURL=chunk-HDCU6QSG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/effects/circle-glow.tsx"],"names":["CircleGlow"],"mappings":";;;;;AA2BA,IAAM,UAAA,GAAmB,KAAA,CAAA,UAAA;AAAA,EACvB,SAASA,WAAAA,CAAW,EAAA,EAAsC,GAAA,EAAK;AAA3C,IAAA,IAAA,EAAA,GAAA,EAAA,EAAE,EAAA,IAAA,GAAO,MAAM,SAAA,EA5BrC,GA4BsB,IAA6B,KAAA,GAAA,SAAA,CAA7B,EAAA,EAA6B,CAA3B,MAAA,EAAa,WAAA,CAAA,CAAA;AACjC,IAAA,uBACE,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA,cAAA,CAAA;AAAA,QACC,GAAA;AAAA,QACA,aAAA,EAAY,MAAA;AAAA,QACZ,WAAA,EAAU,aAAA;AAAA,QACV,WAAA,EAAW,IAAA;AAAA,QACX,SAAA,EAAW,EAAA,CAAG,mBAAA,EAAqB,SAAS;AAAA,OAAA,EACxC,KAAA;AAAA,KACN;AAAA,EAEJ;AACF","file":"chunk-HDCU6QSG.js","sourcesContent":["import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface CircleGlowProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * Tamanho relativo do glow. Controla o `transform: scale()` aplicado ao\n * elemento. Default `\"lg\"` (escala 1.0).\n */\n size?: \"sm\" | \"md\" | \"lg\"\n}\n\n/**\n * Decoracao de glow circular pulsante. Posicionada `absolute` — o **container\n * deve ser `relative`**. Idealmente coloque atras dos cards usando `-z-10`\n * (ou similar) via `className`.\n *\n * Sempre `aria-hidden` e `pointer-events: none` (e puramente visual).\n * Respeita `prefers-reduced-motion`.\n *\n * @example\n * <div className=\"relative\">\n * <CircleGlow className=\"-z-10\" />\n * <div className=\"grid grid-cols-3 gap-6\">{cards}</div>\n * </div>\n */\nconst CircleGlow = React.forwardRef<HTMLDivElement, CircleGlowProps>(\n function CircleGlow({ size = \"lg\", className, ...props }, ref) {\n return (\n <div\n ref={ref}\n aria-hidden=\"true\"\n data-slot=\"circle-glow\"\n data-size={size}\n className={cn(\"cycle-circle-glow\", className)}\n {...props}\n />\n )\n }\n)\n\nexport { CircleGlow }\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
function useReducedMotion() {
|
|
4
|
+
const [reduced, setReduced] = useState(false);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
7
|
+
setReduced(media.matches);
|
|
8
|
+
const handler = (event) => setReduced(event.matches);
|
|
9
|
+
media.addEventListener("change", handler);
|
|
10
|
+
return () => media.removeEventListener("change", handler);
|
|
11
|
+
}, []);
|
|
12
|
+
return reduced;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { useReducedMotion };
|
|
16
|
+
//# sourceMappingURL=chunk-K2NUBNSA.js.map
|
|
17
|
+
//# sourceMappingURL=chunk-K2NUBNSA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-reduced-motion.ts"],"names":[],"mappings":";;AAYO,SAAS,gBAAA,GAA4B;AAC1C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAE5C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA;AAClE,IAAA,UAAA,CAAW,MAAM,OAAO,CAAA;AAExB,IAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAA+B,UAAA,CAAW,MAAM,OAAO,CAAA;AACxE,IAAA,KAAA,CAAM,gBAAA,CAAiB,UAAU,OAAO,CAAA;AACxC,IAAA,OAAO,MAAM,KAAA,CAAM,mBAAA,CAAoB,QAAA,EAAU,OAAO,CAAA;AAAA,EAC1D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,OAAA;AACT","file":"chunk-K2NUBNSA.js","sourcesContent":["\"use client\"\n\nimport { useEffect, useState } from \"react\"\n\n/**\n * Returns `true` quando o usuario habilitou \"prefers-reduced-motion\" no SO.\n * Componentes do `marketing/motion/*` consultam esse hook pra desligar animacoes\n * (resolver para o estado final direto) — requisito WCAG 2.3.3.\n *\n * SSR-safe: comeca em `false` no server e atualiza no client apos mount.\n * Reage a mudancas dinamicas (usuario toggla a preferencia com a aba aberta).\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n\n useEffect(() => {\n const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n setReduced(media.matches)\n\n const handler = (event: MediaQueryListEvent) => setReduced(event.matches)\n media.addEventListener(\"change\", handler)\n return () => media.removeEventListener(\"change\", handler)\n }, [])\n\n return reduced\n}\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
function SmoothAnchor({ headerOffset = 96, duration = 900 }) {
|
|
4
|
+
React.useEffect(() => {
|
|
5
|
+
if (typeof window === "undefined") return;
|
|
6
|
+
if (window.location.hash) {
|
|
7
|
+
const id = window.location.hash.slice(1);
|
|
8
|
+
const el = document.getElementById(id);
|
|
9
|
+
if (el) {
|
|
10
|
+
requestAnimationFrame(() => el.classList.add("anchor-target-pulse"));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function onClick(e) {
|
|
14
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey) return;
|
|
15
|
+
const target = e.target;
|
|
16
|
+
const anchor = target == null ? void 0 : target.closest('a[href^="#"]');
|
|
17
|
+
if (!anchor) return;
|
|
18
|
+
const href = anchor.getAttribute("href");
|
|
19
|
+
if (!href || href === "#") return;
|
|
20
|
+
const id = href.slice(1);
|
|
21
|
+
const el = document.getElementById(id);
|
|
22
|
+
if (!el) return;
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
const prefersReduced = window.matchMedia(
|
|
25
|
+
"(prefers-reduced-motion: reduce)"
|
|
26
|
+
).matches;
|
|
27
|
+
const rect = el.getBoundingClientRect();
|
|
28
|
+
const targetY = window.scrollY + rect.top - headerOffset;
|
|
29
|
+
if (prefersReduced) {
|
|
30
|
+
window.scrollTo(0, targetY);
|
|
31
|
+
} else {
|
|
32
|
+
smoothScrollTo(targetY, duration);
|
|
33
|
+
}
|
|
34
|
+
history.replaceState(null, "", href);
|
|
35
|
+
el.classList.remove("anchor-target-pulse");
|
|
36
|
+
void el.offsetWidth;
|
|
37
|
+
el.classList.add("anchor-target-pulse");
|
|
38
|
+
}
|
|
39
|
+
document.addEventListener("click", onClick);
|
|
40
|
+
return () => document.removeEventListener("click", onClick);
|
|
41
|
+
}, [headerOffset, duration]);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function smoothScrollTo(targetY, duration) {
|
|
45
|
+
const startY = window.scrollY;
|
|
46
|
+
const distance = targetY - startY;
|
|
47
|
+
if (Math.abs(distance) < 2) return;
|
|
48
|
+
const startTime = performance.now();
|
|
49
|
+
function step(now) {
|
|
50
|
+
const elapsed = now - startTime;
|
|
51
|
+
const t = Math.min(elapsed / duration, 1);
|
|
52
|
+
const eased = easeInOutCubic(t);
|
|
53
|
+
window.scrollTo(0, startY + distance * eased);
|
|
54
|
+
if (t < 1) requestAnimationFrame(step);
|
|
55
|
+
}
|
|
56
|
+
requestAnimationFrame(step);
|
|
57
|
+
}
|
|
58
|
+
function easeInOutCubic(t) {
|
|
59
|
+
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { SmoothAnchor };
|
|
63
|
+
//# sourceMappingURL=chunk-NW56QDQM.js.map
|
|
64
|
+
//# sourceMappingURL=chunk-NW56QDQM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/motion/smooth-anchor.tsx"],"names":[],"mappings":";;AAiCA,SAAS,aAAa,EAAE,YAAA,GAAe,EAAA,EAAI,QAAA,GAAW,KAAI,EAAsB;AAC9E,EAAM,gBAAU,MAAM;AACpB,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAGnC,IAAA,IAAI,MAAA,CAAO,SAAS,IAAA,EAAM;AACxB,MAAA,MAAM,EAAA,GAAK,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,MAAM,CAAC,CAAA;AACvC,MAAA,MAAM,EAAA,GAAK,QAAA,CAAS,cAAA,CAAe,EAAE,CAAA;AACrC,MAAA,IAAI,EAAA,EAAI;AACN,QAAA,qBAAA,CAAsB,MAAM,EAAA,CAAG,SAAA,CAAU,GAAA,CAAI,qBAAqB,CAAC,CAAA;AAAA,MACrE;AAAA,IACF;AAEA,IAAA,SAAS,QAAQ,CAAA,EAAe;AAC9B,MAAA,IAAI,CAAA,CAAE,oBAAoB,CAAA,CAAE,MAAA,KAAW,KAAK,CAAA,CAAE,OAAA,IAAW,EAAE,OAAA,EAAS;AACpE,MAAA,MAAM,SAAS,CAAA,CAAE,MAAA;AACjB,MAAA,MAAM,MAAA,GAAS,iCAAQ,OAAA,CAAQ,cAAA,CAAA;AAC/B,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,MAAM,IAAA,GAAO,MAAA,CAAO,YAAA,CAAa,MAAM,CAAA;AACvC,MAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,KAAS,GAAA,EAAK;AAC3B,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AACvB,MAAA,MAAM,EAAA,GAAK,QAAA,CAAS,cAAA,CAAe,EAAE,CAAA;AACrC,MAAA,IAAI,CAAC,EAAA,EAAI;AAET,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAM,iBAAiB,MAAA,CAAO,UAAA;AAAA,QAC5B;AAAA,OACF,CAAE,OAAA;AAEF,MAAA,MAAM,IAAA,GAAO,GAAG,qBAAA,EAAsB;AACtC,MAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,GAAU,IAAA,CAAK,GAAA,GAAM,YAAA;AAE5C,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,MAAA,CAAO,QAAA,CAAS,GAAG,OAAO,CAAA;AAAA,MAC5B,CAAA,MAAO;AACL,QAAA,cAAA,CAAe,SAAS,QAAQ,CAAA;AAAA,MAClC;AAEA,MAAA,OAAA,CAAQ,YAAA,CAAa,IAAA,EAAM,EAAA,EAAI,IAAI,CAAA;AAEnC,MAAA,EAAA,CAAG,SAAA,CAAU,OAAO,qBAAqB,CAAA;AACzC,MAAA,KAAK,EAAA,CAAG,WAAA;AACR,MAAA,EAAA,CAAG,SAAA,CAAU,IAAI,qBAAqB,CAAA;AAAA,IACxC;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,SAAS,OAAO,CAAA;AAC1C,IAAA,OAAO,MAAM,QAAA,CAAS,mBAAA,CAAoB,OAAA,EAAS,OAAO,CAAA;AAAA,EAC5D,CAAA,EAAG,CAAC,YAAA,EAAc,QAAQ,CAAC,CAAA;AAE3B,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,cAAA,CAAe,SAAiB,QAAA,EAAkB;AACzD,EAAA,MAAM,SAAS,MAAA,CAAO,OAAA;AACtB,EAAA,MAAM,WAAW,OAAA,GAAU,MAAA;AAC3B,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,QAAQ,CAAA,GAAI,CAAA,EAAG;AAC5B,EAAA,MAAM,SAAA,GAAY,YAAY,GAAA,EAAI;AAElC,EAAA,SAAS,KAAK,GAAA,EAAa;AACzB,IAAA,MAAM,UAAU,GAAA,GAAM,SAAA;AACtB,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,OAAA,GAAU,UAAU,CAAC,CAAA;AACxC,IAAA,MAAM,KAAA,GAAQ,eAAe,CAAC,CAAA;AAC9B,IAAA,MAAA,CAAO,QAAA,CAAS,CAAA,EAAG,MAAA,GAAS,QAAA,GAAW,KAAK,CAAA;AAC5C,IAAA,IAAI,CAAA,GAAI,CAAA,EAAG,qBAAA,CAAsB,IAAI,CAAA;AAAA,EACvC;AAEA,EAAA,qBAAA,CAAsB,IAAI,CAAA;AAC5B;AAEA,SAAS,eAAe,CAAA,EAAmB;AACzC,EAAA,OAAO,CAAA,GAAI,GAAA,GAAM,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,EAAA,GAAK,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA,GAAI,CAAA;AACjE","file":"chunk-NW56QDQM.js","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nexport interface SmoothAnchorProps {\n /** Offset em px pra compensar header sticky. Default `96`. */\n headerOffset?: number\n /** Duracao do scroll em ms. Default `900`. */\n duration?: number\n}\n\n/**\n * Intercepta cliques em links de hash (`<a href=\"#algo\">`) e faz scroll suave\n * com easing `easeInOutCubic` em vez do `scroll-behavior: smooth` nativo\n * (que tem duracao curta e easing fixo).\n *\n * Renderiza `null` (so registra listener global). Monte UMA VEZ por app,\n * tipicamente no `<RootLayout>` ou na pagina raiz da landing.\n *\n * Comportamento:\n * - Ignora clicks com modificadores (cmd/ctrl/middle-button).\n * - Atualiza URL via `history.replaceState` sem disparar scroll nativo.\n * - Adiciona classe `anchor-target-pulse` no destino apos chegar (animacao\n * visual opcional, definida em `globals.css`).\n * - Respeita `prefers-reduced-motion` (scroll instantaneo, sem easing).\n *\n * @example\n * // app/layout.tsx\n * <body>\n * <SmoothAnchor headerOffset={64} />\n * {children}\n * </body>\n */\nfunction SmoothAnchor({ headerOffset = 96, duration = 900 }: SmoothAnchorProps) {\n React.useEffect(() => {\n if (typeof window === \"undefined\") return\n\n // Se a pagina abre ja com hash, pulsa o destino apos o scroll inicial.\n if (window.location.hash) {\n const id = window.location.hash.slice(1)\n const el = document.getElementById(id)\n if (el) {\n requestAnimationFrame(() => el.classList.add(\"anchor-target-pulse\"))\n }\n }\n\n function onClick(e: MouseEvent) {\n if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey) return\n const target = e.target as HTMLElement | null\n const anchor = target?.closest('a[href^=\"#\"]') as HTMLAnchorElement | null\n if (!anchor) return\n const href = anchor.getAttribute(\"href\")\n if (!href || href === \"#\") return\n const id = href.slice(1)\n const el = document.getElementById(id)\n if (!el) return\n\n e.preventDefault()\n const prefersReduced = window.matchMedia(\n \"(prefers-reduced-motion: reduce)\"\n ).matches\n\n const rect = el.getBoundingClientRect()\n const targetY = window.scrollY + rect.top - headerOffset\n\n if (prefersReduced) {\n window.scrollTo(0, targetY)\n } else {\n smoothScrollTo(targetY, duration)\n }\n\n history.replaceState(null, \"\", href)\n // Reativa pulse mesmo se a URL ja for a mesma\n el.classList.remove(\"anchor-target-pulse\")\n void el.offsetWidth\n el.classList.add(\"anchor-target-pulse\")\n }\n\n document.addEventListener(\"click\", onClick)\n return () => document.removeEventListener(\"click\", onClick)\n }, [headerOffset, duration])\n\n return null\n}\n\nfunction smoothScrollTo(targetY: number, duration: number) {\n const startY = window.scrollY\n const distance = targetY - startY\n if (Math.abs(distance) < 2) return\n const startTime = performance.now()\n\n function step(now: number) {\n const elapsed = now - startTime\n const t = Math.min(elapsed / duration, 1)\n const eased = easeInOutCubic(t)\n window.scrollTo(0, startY + distance * eased)\n if (t < 1) requestAnimationFrame(step)\n }\n\n requestAnimationFrame(step)\n}\n\nfunction easeInOutCubic(t: number): number {\n return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2\n}\n\nexport { SmoothAnchor }\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var Section = React.forwardRef(function Section2(_a, ref) {
|
|
7
|
+
var _b = _a, { as = "section", variant = "default", tone = "dark", id, className } = _b, props = __objRest(_b, ["as", "variant", "tone", "id", "className"]);
|
|
8
|
+
const Tag = as;
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
Tag,
|
|
11
|
+
__spreadValues({
|
|
12
|
+
ref,
|
|
13
|
+
id,
|
|
14
|
+
"data-slot": "marketing-section",
|
|
15
|
+
"data-variant": variant,
|
|
16
|
+
"data-tone": tone,
|
|
17
|
+
className: cn(
|
|
18
|
+
// base
|
|
19
|
+
"relative w-full",
|
|
20
|
+
// variants
|
|
21
|
+
variant === "hero" && "min-h-screen flex flex-col scroll-mt-0",
|
|
22
|
+
variant === "default" && "py-20 md:py-28 scroll-mt-8",
|
|
23
|
+
// tone
|
|
24
|
+
tone === "light" && "light-surface",
|
|
25
|
+
className
|
|
26
|
+
)
|
|
27
|
+
}, props)
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export { Section };
|
|
32
|
+
//# sourceMappingURL=chunk-NZXP7BAH.js.map
|
|
33
|
+
//# sourceMappingURL=chunk-NZXP7BAH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/layout/section.tsx"],"names":["Section"],"mappings":";;;;;AAoCA,IAAM,OAAA,GAAgB,KAAA,CAAA,UAAA,CAAsC,SAASA,QAAAA,CACnE,IACA,GAAA,EACA;AAFA,EAAA,IAAA,EAAA,GAAA,EAAA,EAAE,OAAK,SAAA,EAAW,OAAA,GAAU,SAAA,EAAW,IAAA,GAAO,QAAQ,EAAA,EAAI,SAAA,EArC5D,GAqCE,EAAA,EAAwE,kBAAxE,EAAA,EAAwE,CAAtE,IAAA,EAAgB,SAAA,EAAqB,QAAe,IAAA,EAAI,WAAA,CAAA,CAAA;AAG1D,EAAA,MAAM,GAAA,GAAM,EAAA;AACZ,EAAA,uBACE,GAAA;AAAA,IAAC,GAAA;AAAA,IAAA,cAAA,CAAA;AAAA,MACC,GAAA;AAAA,MACA,EAAA;AAAA,MACA,WAAA,EAAU,mBAAA;AAAA,MACV,cAAA,EAAc,OAAA;AAAA,MACd,WAAA,EAAW,IAAA;AAAA,MACX,SAAA,EAAW,EAAA;AAAA;AAAA,QAET,iBAAA;AAAA;AAAA,QAEA,YAAY,MAAA,IAAU,wCAAA;AAAA,QACtB,YAAY,SAAA,IAAa,4BAAA;AAAA;AAAA,QAEzB,SAAS,OAAA,IAAW,eAAA;AAAA,QACpB;AAAA;AACF,KAAA,EACI,KAAA;AAAA,GACN;AAEJ,CAAC","file":"chunk-NZXP7BAH.js","sourcesContent":["import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface SectionProps extends React.HTMLAttributes<HTMLElement> {\n /** Tag HTML renderizada. Default `\"section\"`. */\n as?: \"section\" | \"div\"\n /**\n * Forma da dobra:\n * - `\"hero\"`: `min-h-screen flex flex-col` (primeira dobra acima do fold).\n * - `\"default\"`: padding vertical generoso (`py-20 md:py-28`) — usado nas demais.\n */\n variant?: \"hero\" | \"default\"\n /**\n * Aplica `.light-surface` quando `\"light\"` — sobrescreve os tokens semanticos\n * pra um fundo claro dentro de tema dark global. `\"dark\"` nao aplica nada\n * (segue o tema do root).\n */\n tone?: \"dark\" | \"light\"\n}\n\n/**\n * Wrapper de uma \"dobra\" de landing page. Generaliza o `HomeSection` do\n * `fluencypass-corporate-site`: padroniza padding/altura via `variant` e\n * permite alternar bg dark/light por dobra via `tone`.\n *\n * Pareie com `SectionHeading`, `SectionEyebrow`, `SectionSubtitle` pra montar\n * o cabecalho da dobra com tipografia consistente.\n *\n * @example\n * <Section id=\"pilares\" variant=\"default\" tone=\"light\">\n * <SectionEyebrow>O que oferecemos</SectionEyebrow>\n * <SectionHeading>3 pilares essenciais</SectionHeading>\n * <div className=\"grid md:grid-cols-3 gap-6\">...</div>\n * </Section>\n */\nconst Section = React.forwardRef<HTMLElement, SectionProps>(function Section(\n { as = \"section\", variant = \"default\", tone = \"dark\", id, className, ...props },\n ref\n) {\n const Tag = as as React.ElementType\n return (\n <Tag\n ref={ref}\n id={id}\n data-slot=\"marketing-section\"\n data-variant={variant}\n data-tone={tone}\n className={cn(\n // base\n \"relative w-full\",\n // variants\n variant === \"hero\" && \"min-h-screen flex flex-col scroll-mt-0\",\n variant === \"default\" && \"py-20 md:py-28 scroll-mt-8\",\n // tone\n tone === \"light\" && \"light-surface\",\n className\n )}\n {...props}\n />\n )\n})\n\nexport { Section }\n"]}
|