@breathe-ds/ui 0.1.1 → 0.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.
- package/LICENSE +21 -0
- package/README.md +219 -142
- package/components/Accordion.tsx +17 -14
- package/components/AnchorNav.tsx +33 -21
- package/components/Avatar.tsx +37 -0
- package/components/BackgroundVideo.tsx +14 -14
- package/components/BottomNav.tsx +70 -69
- package/components/Breadcrumb.tsx +45 -0
- package/components/BreatheMark.tsx +12 -24
- package/components/Button.tsx +12 -12
- package/components/Byline.tsx +103 -0
- package/components/CaptionBand.tsx +74 -0
- package/components/Card.tsx +5 -5
- package/components/Container.tsx +4 -4
- package/components/CtaCard.tsx +52 -0
- package/components/Curtain.tsx +152 -0
- package/components/FactStrip.tsx +55 -0
- package/components/Footer.tsx +169 -77
- package/components/Header.tsx +32 -17
- package/components/Heading.tsx +86 -0
- package/components/InfoCard.tsx +116 -0
- package/components/LanguageSwitcher.tsx +139 -0
- package/components/List.tsx +81 -0
- package/components/Lockup.tsx +38 -55
- package/components/MapCard.tsx +158 -0
- package/components/MediaCard.tsx +266 -0
- package/components/PageVideoHero.tsx +53 -53
- package/components/PersonChip.tsx +49 -0
- package/components/Pill.tsx +42 -0
- package/components/PillList.tsx +21 -48
- package/components/ProfessionalCard.tsx +341 -0
- package/components/ProfessionalModal.tsx +243 -0
- package/components/Rows.tsx +131 -0
- package/components/SectionKicker.tsx +3 -3
- package/components/TestimonialCard.tsx +138 -0
- package/components/Text.tsx +31 -0
- package/components/Timeline.tsx +69 -0
- package/components/VideoInviteCard.tsx +168 -0
- package/components/VideoStoryCard.tsx +382 -0
- package/components/icons.tsx +6 -6
- package/components/marks/creeta.tsx +22 -22
- package/components/marks/ttcab.tsx +13 -13
- package/package.json +28 -5
- package/theme.css +88 -22
- package/tokens/creeta.css +14 -12
- package/tokens/neutral.css +16 -10
- package/tokens/ttcab.css +9 -8
- package/components/Cortina.tsx +0 -152
package/theme.css
CHANGED
|
@@ -1,40 +1,106 @@
|
|
|
1
1
|
/*
|
|
2
2
|
@breathe-ds/ui — theme.css (tokens-first, v0.1)
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Token contract of the Cuidado Global family (RTA/Mariangela ecosystem).
|
|
5
|
+
COLORS are ROLES: the value of each one comes from a `--cg-*` CSS var that
|
|
6
|
+
EACH SITE defines (ttcab petrol/cream, creeta petrol/mist/magenta…).
|
|
7
|
+
This way the sites inherit the SAME structure and names, with their own palette.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Usage in a site's globals.css (Tailwind v4):
|
|
10
10
|
@import "tailwindcss";
|
|
11
11
|
@import "@breathe-ds/ui/theme.css";
|
|
12
|
-
@import "@breathe-ds/ui/tokens/ttcab.css"; (
|
|
12
|
+
@import "@breathe-ds/ui/tokens/ttcab.css"; (or define the --cg-* inline)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Fixed scales (typography roles, radius, motion) are shared here.
|
|
15
|
+
Measured contrasts of each palette: see README.md and the tokens/* files.
|
|
16
16
|
*/
|
|
17
17
|
@theme {
|
|
18
|
-
/* ──
|
|
19
|
-
--color-surface: var(--cg-surface); /*
|
|
20
|
-
--color-surface-panel: var(--cg-surface-panel); /*
|
|
21
|
-
--color-
|
|
22
|
-
--color-ink
|
|
23
|
-
--color-ink-
|
|
24
|
-
--color-
|
|
25
|
-
--color-action
|
|
26
|
-
--color-
|
|
27
|
-
--color-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
/* ── Colors (roles) — value comes from the site's --cg-* ── */
|
|
19
|
+
--color-surface: var(--cg-surface); /* base background */
|
|
20
|
+
--color-surface-panel: var(--cg-surface-panel); /* dark panel (footer/CTA) */
|
|
21
|
+
--color-card: var(--cg-card, #ffffff); /* raised surface: cards, pills, menus over the page */
|
|
22
|
+
--color-ink: var(--cg-ink); /* primary text / headings */
|
|
23
|
+
--color-ink-soft: var(--cg-ink-soft); /* secondary text */
|
|
24
|
+
--color-ink-body: var(--cg-ink-body); /* body copy */
|
|
25
|
+
--color-action: var(--cg-action); /* CTA/highlight — never plain text */
|
|
26
|
+
--color-action-text: var(--cg-action-text); /* text in the action color (AA) */
|
|
27
|
+
--color-on-action: var(--cg-on-action); /* text ON the action color (button) */
|
|
28
|
+
--color-line: var(--cg-line); /* hairlines/borders */
|
|
29
|
+
|
|
30
|
+
/* ── Typography (roles; actual family comes from --cg-font-*) ── */
|
|
30
31
|
--font-heading: var(--cg-font-heading, system-ui, sans-serif);
|
|
31
32
|
--font-body: var(--cg-font-body, Arial, system-ui, sans-serif);
|
|
32
33
|
|
|
33
|
-
/* ──
|
|
34
|
+
/* ── Radius ── */
|
|
34
35
|
--radius-xl: 24px;
|
|
35
36
|
--radius-2xl: 28px;
|
|
36
37
|
--radius-pill: 9999px;
|
|
37
38
|
|
|
38
|
-
/* ── Motion —
|
|
39
|
+
/* ── Motion — the family's signature curve ── */
|
|
39
40
|
--ease-cg: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
40
41
|
}
|
|
42
|
+
|
|
43
|
+
/* ── Base behaviour the whole family shares ──
|
|
44
|
+
Smooth scrolling for anchors (AnchorNav, "On this page", BottomNav); off
|
|
45
|
+
for people who asked for reduced motion. The header sticks at the top
|
|
46
|
+
(~5rem) and, on phones, the BottomNav sticks at the bottom: without this
|
|
47
|
+
padding the control that receives keyboard focus would stop hidden behind
|
|
48
|
+
them (WCAG 2.4.11). Anchored sections may still add their own scroll-margin. */
|
|
49
|
+
@layer base {
|
|
50
|
+
html {
|
|
51
|
+
scroll-behavior: smooth;
|
|
52
|
+
scroll-padding-top: 6rem;
|
|
53
|
+
scroll-padding-bottom: 7rem;
|
|
54
|
+
}
|
|
55
|
+
@media (prefers-reduced-motion: reduce) {
|
|
56
|
+
html {
|
|
57
|
+
scroll-behavior: auto;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Outbound/anchor arrow on links: takes a small step toward where the link
|
|
63
|
+
goes, on hover. Used by Footer, List, Byline, PersonChip, PillList. */
|
|
64
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
65
|
+
.cg-arrow-go {
|
|
66
|
+
transition: transform 150ms var(--ease-cg);
|
|
67
|
+
}
|
|
68
|
+
a:hover .cg-arrow-go,
|
|
69
|
+
.group:hover .cg-arrow-go {
|
|
70
|
+
transform: translate(2px, -2px);
|
|
71
|
+
}
|
|
72
|
+
.cg-arrow-down {
|
|
73
|
+
transition: transform 150ms var(--ease-cg);
|
|
74
|
+
}
|
|
75
|
+
a:hover .cg-arrow-down,
|
|
76
|
+
.group:hover .cg-arrow-down {
|
|
77
|
+
transform: translateY(2px);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ── BreatheMark: the symbol "breathes" (scale pulse); off under reduced motion ── */
|
|
82
|
+
@keyframes breathe-mark-inhale {
|
|
83
|
+
0%, 100% { transform: scale(0.93); }
|
|
84
|
+
50% { transform: scale(1.05); }
|
|
85
|
+
}
|
|
86
|
+
.breathe-mark__g {
|
|
87
|
+
transform-box: fill-box;
|
|
88
|
+
transform-origin: center;
|
|
89
|
+
animation: breathe-mark-inhale var(--breathe-dur, 6s) cubic-bezier(0.37, 0, 0.63, 1) infinite;
|
|
90
|
+
}
|
|
91
|
+
.breathe-mark--static .breathe-mark__g { animation: none; }
|
|
92
|
+
@media (prefers-reduced-motion: reduce) { .breathe-mark__g { animation: none; } }
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/* ── Lockup entrance: symbol (scale + slight turn) → name (slides in) → tagline.
|
|
96
|
+
Same vocabulary as the Curtain; under reduced motion only a short fade. ── */
|
|
97
|
+
.cg-lockup--entrance .cg-lockup__mark{ animation: cg-lockup-mark 500ms var(--ease-cg,cubic-bezier(.2,.8,.2,1)) var(--cg-entrance-delay,0ms) both; }
|
|
98
|
+
.cg-lockup--entrance .cg-lockup__name{ animation: cg-lockup-name 420ms var(--ease-cg,cubic-bezier(.2,.8,.2,1)) calc(var(--cg-entrance-delay,0ms) + 420ms) both; }
|
|
99
|
+
.cg-lockup--entrance .cg-lockup__tag{ animation: cg-lockup-tag 360ms ease calc(var(--cg-entrance-delay,0ms) + 700ms) both; }
|
|
100
|
+
@keyframes cg-lockup-mark{ from{ opacity:0; transform:scale(.6) rotate(-16deg); } }
|
|
101
|
+
@keyframes cg-lockup-name{ from{ opacity:0; transform:translateX(-8px); } }
|
|
102
|
+
@keyframes cg-lockup-tag{ from{ opacity:0; transform:translateY(4px); } }
|
|
103
|
+
@media (prefers-reduced-motion:reduce){
|
|
104
|
+
.cg-lockup--entrance .cg-lockup__mark, .cg-lockup--entrance .cg-lockup__name, .cg-lockup--entrance .cg-lockup__tag{ animation: cg-lockup-fade 240ms ease var(--cg-entrance-delay,0ms) both; }
|
|
105
|
+
@keyframes cg-lockup-fade{ from{ opacity:0; } }
|
|
106
|
+
}
|
package/tokens/creeta.css
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- surface #eef3f4 / ink #144753 ............ ~9
|
|
5
|
-
- surface #eef3f4 / ink-soft #
|
|
6
|
-
|
|
2
|
+
Creeta palette (RTA clinic in Floripa) — petrol + mist + magenta.
|
|
3
|
+
Values from novo.creeta. Measured contrasts (WCAG formula):
|
|
4
|
+
- surface #eef3f4 / ink #144753 ............ ~9.1:1 (AAA)
|
|
5
|
+
- surface #eef3f4 / ink-soft #266675 ....... 5.79:1 (AA) — the site's "teal-escuro":
|
|
6
|
+
second tone of two-tone headings, eyebrows, strong links
|
|
7
|
+
- surface #eef3f4 / ink-body #1e4f5b ....... 8.07:1 (AAA)
|
|
8
|
+
- action #ab2781 (magenta) is the CTA ("Agendar"); text on it = white.
|
|
7
9
|
*/
|
|
8
10
|
:root {
|
|
9
|
-
--cg-surface: #eef3f4; /*
|
|
10
|
-
--cg-surface-panel: #144753; /*
|
|
11
|
-
--cg-
|
|
12
|
-
--cg-ink
|
|
11
|
+
--cg-surface: #eef3f4; /* mist */
|
|
12
|
+
--cg-surface-panel: #144753; /* petrol */
|
|
13
|
+
--cg-card: #ffffff; /* cards, pills and menus over the page */
|
|
14
|
+
--cg-ink: #144753; /* petrol */
|
|
15
|
+
--cg-ink-soft: #266675; /* teal-escuro */
|
|
13
16
|
--cg-ink-body: #1e4f5b;
|
|
14
17
|
--cg-action: #ab2781; /* magenta (Agendar) */
|
|
15
|
-
--cg-action-text: #8f1f6c; /* magenta
|
|
16
|
-
--cg-on-action: #ffffff; /*
|
|
18
|
+
--cg-action-text: #8f1f6c; /* darker magenta for text */
|
|
19
|
+
--cg-on-action: #ffffff; /* white on magenta */
|
|
17
20
|
--cg-line: rgb(20 71 83 / 0.16);
|
|
18
21
|
--cg-font-heading: var(--font-quicksand), system-ui, sans-serif; /* Quicksand */
|
|
19
22
|
--cg-font-body: var(--font-figtree), system-ui, sans-serif; /* Figtree */
|
|
20
23
|
}
|
|
21
|
-
CSS
|
package/tokens/neutral.css
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
NEUTRAL palette — the Breathe default. The design system is neutral on
|
|
3
|
+
purpose: color comes from EACH application (ttcab, creeta…). Use this as the
|
|
4
|
+
base and override the --cg-* in your site's :root. Monochrome (gray/near-black)
|
|
5
|
+
to make it clear it is a "bring your own color" placeholder.
|
|
6
|
+
The page is a light gray and the cards are white: the family is tonal (mist on
|
|
7
|
+
creeta, cream on ttcab), so the neutral palette is too — a white page would
|
|
8
|
+
swallow the white cards.
|
|
9
|
+
- surface #f4f4f5 / ink #18181b ............ 16.1:1 (AAA)
|
|
10
|
+
- surface #f4f4f5 / ink-soft #65656e ....... 5.25:1 (AA)
|
|
11
|
+
- surface #f4f4f5 / ink-body #3f3f46 ....... 9.5:1 (AAA)
|
|
12
|
+
- card #ffffff / ink-soft #65656e .......... 5.77:1 (AA)
|
|
8
13
|
*/
|
|
9
14
|
:root {
|
|
10
|
-
--cg-surface: #
|
|
15
|
+
--cg-surface: #f4f4f5; /* page: light gray, so white cards read */
|
|
11
16
|
--cg-surface-panel: #18181b;
|
|
17
|
+
--cg-card: #ffffff;
|
|
12
18
|
--cg-ink: #18181b;
|
|
13
|
-
--cg-ink-soft: #
|
|
19
|
+
--cg-ink-soft: #65656e;
|
|
14
20
|
--cg-ink-body: #3f3f46;
|
|
15
21
|
--cg-action: #18181b;
|
|
16
22
|
--cg-action-text: #52525b;
|
|
17
23
|
--cg-on-action: #ffffff;
|
|
18
24
|
--cg-line: rgb(0 0 0 / 0.12);
|
|
19
|
-
--cg-font-heading: var(--font-quicksand), system-ui, sans-serif; /* Quicksand (
|
|
20
|
-
--cg-font-body: var(--font-figtree), system-ui, sans-serif; /* Figtree (
|
|
25
|
+
--cg-font-heading: var(--font-quicksand), system-ui, sans-serif; /* Quicksand (fixed across the family) */
|
|
26
|
+
--cg-font-body: var(--font-figtree), system-ui, sans-serif; /* Figtree (fixed across the family) */
|
|
21
27
|
}
|
package/tokens/ttcab.css
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
3
|
-
- surface #fbf5e9 / ink #144753 ............ 9
|
|
4
|
-
- surface #fbf5e9 / ink-soft #54707a ....... 4
|
|
5
|
-
- surface #fbf5e9 / ink-body #1e4f5b ....... 8
|
|
6
|
-
- surface #fbf5e9 / action-text #9a5d07 .... 4
|
|
7
|
-
- surface-panel #144753 / surface (
|
|
8
|
-
- action #f6a22a
|
|
2
|
+
TTCAB palette (petrol + cream). Measured contrasts:
|
|
3
|
+
- surface #fbf5e9 / ink #144753 ............ 9.41:1 (AAA)
|
|
4
|
+
- surface #fbf5e9 / ink-soft #54707a ....... 4.86:1 (AA)
|
|
5
|
+
- surface #fbf5e9 / ink-body #1e4f5b ....... 8.32:1 (AAA)
|
|
6
|
+
- surface #fbf5e9 / action-text #9a5d07 .... 4.90:1 (AA)
|
|
7
|
+
- surface-panel #144753 / surface (text) ... 9.41:1 (AAA)
|
|
8
|
+
- action #f6a22a is DECORATIVE/CTA — never text on cream.
|
|
9
9
|
*/
|
|
10
10
|
:root {
|
|
11
11
|
--cg-surface: #fbf5e9;
|
|
12
12
|
--cg-surface-panel: #144753;
|
|
13
|
+
--cg-card: #ffffff; /* cards, pills and menus over the page */
|
|
13
14
|
--cg-ink: #144753;
|
|
14
15
|
--cg-ink-soft: #54707a;
|
|
15
16
|
--cg-ink-body: #1e4f5b;
|
|
16
17
|
--cg-action: #f6a22a;
|
|
17
|
-
--cg-on-action: #144753; /* ink
|
|
18
|
+
--cg-on-action: #144753; /* ink on orange */
|
|
18
19
|
--cg-action-text: #9a5d07;
|
|
19
20
|
--cg-line: rgb(20 71 83 / 0.16);
|
|
20
21
|
--cg-font-heading: var(--font-quicksand), system-ui, sans-serif; /* Quicksand */
|
package/components/Cortina.tsx
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
|
|
4
|
-
import { BreatheMark } from "./BreatheMark";
|
|
5
|
-
|
|
6
|
-
type Props = {
|
|
7
|
-
/** Nome da marca (ex.: "creeta", "TTCAB", "Breathe"). */
|
|
8
|
-
name: ReactNode;
|
|
9
|
-
/** Linha abaixo do nome (ex.: "Centro de Reequilíbrio Toracoabdominal"). */
|
|
10
|
-
tagline?: ReactNode;
|
|
11
|
-
/** Símbolo à esquerda do nome. Padrão: o BreatheMark (anel + ponto). Cada
|
|
12
|
-
* site injeta o seu logotipo aqui. */
|
|
13
|
-
logo?: ReactNode;
|
|
14
|
-
/** Quando `name` é um wordmark svg: altura dele como fração da altura do
|
|
15
|
-
* símbolo, do manual da marca (ex.: `CREETA_LOCKUP.wordmarkPerSymbol`). */
|
|
16
|
-
wordmarkRatio?: number;
|
|
17
|
-
/** Lado do símbolo. Padrão `left`; o TTCAB é `right` (`TTCAB_LOCKUP.symbolSide`). */
|
|
18
|
-
symbolSide?: "left" | "right";
|
|
19
|
-
/** Chave do sessionStorage (quando há o script de cabeça). */
|
|
20
|
-
storageKey?: string;
|
|
21
|
-
/** Chamado quando a cortina termina de subir. */
|
|
22
|
-
onDone?: () => void;
|
|
23
|
-
className?: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Cortina de entrada da família Cuidado Global — tela cheia, na cor do site
|
|
28
|
-
* (tokens surface/ink), com o logotipo + nome se formando no centro e a
|
|
29
|
-
* tagline embaixo; depois ela sobe e mostra a página. Espelha a `Cortina` do
|
|
30
|
-
* creeta, mas neutra: a paleta e o logotipo vêm de cada app.
|
|
31
|
-
*
|
|
32
|
-
* - É toda em CSS (estilo embutido): sem JavaScript ela sobe do mesmo jeito,
|
|
33
|
-
* então nunca fica presa na frente do site.
|
|
34
|
-
* - Não recebe clique nem foco e não existe para leitor de tela (aria-hidden).
|
|
35
|
-
* - Com movimento reduzido (ou impressão) não aparece.
|
|
36
|
-
* - Para aparecer só na 1ª visita da sessão, ponha `cortinaHeadScript()` no
|
|
37
|
-
* <head> (antes da pintura): em visita repetida o <html> já nasce com
|
|
38
|
-
* data-cg-cortina="vista" e a cortina não é desenhada — sem piscar.
|
|
39
|
-
*
|
|
40
|
-
* Ao terminar de subir, o <html> ganha data-cg-cortina-fim (para o topo das
|
|
41
|
-
* páginas "pousar" só depois dela, nunca numa troca de tela) e chama onDone.
|
|
42
|
-
*/
|
|
43
|
-
export function Cortina({ name, tagline, logo, wordmarkRatio, symbolSide = "left", storageKey, onDone, className }: Props) {
|
|
44
|
-
const el = useRef<HTMLDivElement>(null);
|
|
45
|
-
const [gone, setGone] = useState(false);
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
const node = el.current;
|
|
49
|
-
if (!node) return;
|
|
50
|
-
const aoTerminar = (e: AnimationEvent) => {
|
|
51
|
-
if (e.animationName !== "cg-cortina-sobe") return;
|
|
52
|
-
document.documentElement.setAttribute("data-cg-cortina-fim", "");
|
|
53
|
-
try {
|
|
54
|
-
if (storageKey) sessionStorage.setItem(storageKey, "1");
|
|
55
|
-
} catch {
|
|
56
|
-
/* sessionStorage bloqueado: a cortina só aparece de novo, nada quebra */
|
|
57
|
-
}
|
|
58
|
-
onDone?.();
|
|
59
|
-
setGone(true);
|
|
60
|
-
};
|
|
61
|
-
node.addEventListener("animationend", aoTerminar);
|
|
62
|
-
return () => node.removeEventListener("animationend", aoTerminar);
|
|
63
|
-
}, [onDone, storageKey]);
|
|
64
|
-
|
|
65
|
-
if (gone) return null;
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div ref={el} className={`cg-cortina${className ? ` ${className}` : ""}`} aria-hidden="true">
|
|
69
|
-
<style>{CORTINA_CSS}</style>
|
|
70
|
-
{/* Símbolo à esquerda: ele abraça o bloco nome+tagline. À direita (TTCAB):
|
|
71
|
-
ele encosta no nome, na mesma linha, e a tagline fica embaixo — senão
|
|
72
|
-
o símbolo iria parar depois da tagline, longe do wordmark. */}
|
|
73
|
-
{(() => {
|
|
74
|
-
const mark = <span className="cg-cortina__mark">{logo ?? <BreatheMark size={60} duration={5} />}</span>;
|
|
75
|
-
const nameEl = (
|
|
76
|
-
<span
|
|
77
|
-
className="cg-cortina__name"
|
|
78
|
-
style={wordmarkRatio ? ({ "--cg-wordmark": `calc(var(--cg-cortina-mark) * ${wordmarkRatio})` } as CSSProperties) : undefined}
|
|
79
|
-
>
|
|
80
|
-
{name}
|
|
81
|
-
</span>
|
|
82
|
-
);
|
|
83
|
-
const tag = tagline ? <span className="cg-cortina__tag">{tagline}</span> : null;
|
|
84
|
-
return symbolSide === "right" ? (
|
|
85
|
-
<div className="cg-cortina__lockup cg-cortina__lockup--right">
|
|
86
|
-
<span className="cg-cortina__row">{nameEl}{mark}</span>
|
|
87
|
-
{tag}
|
|
88
|
-
</div>
|
|
89
|
-
) : (
|
|
90
|
-
<div className="cg-cortina__lockup">
|
|
91
|
-
{mark}
|
|
92
|
-
<span className="cg-cortina__text">{nameEl}{tag}</span>
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
})()}
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Script para o <head> (antes da pintura): marca a cortina como vista nesta
|
|
102
|
-
* sessão, para não repetir ao navegar nem ao recarregar. Sem ele, a cortina
|
|
103
|
-
* aparece a cada carregamento.
|
|
104
|
-
* // Next (app/layout):
|
|
105
|
-
* <script dangerouslySetInnerHTML={{ __html: cortinaHeadScript("cg-cortina") }} />
|
|
106
|
-
*/
|
|
107
|
-
export function cortinaHeadScript(storageKey = "cg-cortina") {
|
|
108
|
-
const k = JSON.stringify(storageKey);
|
|
109
|
-
return `try{var d=document.documentElement;if(sessionStorage.getItem(${k})){d.setAttribute('data-cg-cortina','vista')}else{sessionStorage.setItem(${k},'1')}}catch(e){}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const CORTINA_CSS = `
|
|
113
|
-
.cg-cortina{
|
|
114
|
-
--cg-cortina-mark: clamp(40px,7.4vw,60px); /* altura do símbolo */
|
|
115
|
-
position:fixed; inset:0 0 -40px; padding-bottom:40px;
|
|
116
|
-
z-index:120; display:grid; place-items:center;
|
|
117
|
-
border-radius:0 0 32px 32px;
|
|
118
|
-
background:var(--color-surface,#fff); color:var(--color-ink,#14323a);
|
|
119
|
-
box-shadow:0 24px 60px -20px rgba(0,0,0,.28);
|
|
120
|
-
pointer-events:none;
|
|
121
|
-
animation:cg-cortina-sobe 700ms cubic-bezier(.77,0,.175,1) 2200ms both;
|
|
122
|
-
}
|
|
123
|
-
/* gap ≈ 1/3 do corpo do nome: o anel é curvo, então o vão óptico lê um pouco maior que o geométrico */
|
|
124
|
-
.cg-cortina__lockup{ display:flex; align-items:center; gap:clamp(10px,1.6vw,16px); padding:0 24px; }
|
|
125
|
-
.cg-cortina__lockup--right{ flex-direction:column; align-items:flex-start; }
|
|
126
|
-
.cg-cortina__row{ display:flex; align-items:center; gap:clamp(10px,1.6vw,16px); }
|
|
127
|
-
.cg-cortina__lockup--right .cg-cortina__name{ animation-name:cg-cortina-nome-dir; }
|
|
128
|
-
.cg-cortina__mark{ display:inline-flex; color:var(--color-ink,#14323a);
|
|
129
|
-
animation:cg-cortina-mark 700ms var(--ease-cg,cubic-bezier(.2,.8,.2,1)) both; }
|
|
130
|
-
/* símbolo ~1.25× o corpo do nome (a caixa do SVG é justa ao glifo), fluido.
|
|
131
|
-
Por altura: um símbolo injetado não-quadrado (TTCAB) mantém a proporção. */
|
|
132
|
-
.cg-cortina__mark svg{ height:var(--cg-cortina-mark); width:auto; }
|
|
133
|
-
.cg-cortina__text{ display:flex; flex-direction:column; min-width:0; }
|
|
134
|
-
.cg-cortina__name{
|
|
135
|
-
font-family:var(--font-heading,system-ui,sans-serif);
|
|
136
|
-
font-weight:700; font-size:clamp(2rem,6vw,3rem); line-height:1; letter-spacing:-.02em;
|
|
137
|
-
animation:cg-cortina-nome 600ms var(--ease-cg,cubic-bezier(.2,.8,.2,1)) 480ms both;
|
|
138
|
-
}
|
|
139
|
-
.cg-cortina__tag{
|
|
140
|
-
margin-top:.55rem; font-family:var(--font-body,system-ui,sans-serif);
|
|
141
|
-
font-size:clamp(.8rem,2.4vw,1rem); line-height:1.3; color:var(--color-ink-soft,#54707a);
|
|
142
|
-
animation:cg-cortina-tag 500ms ease 820ms both;
|
|
143
|
-
}
|
|
144
|
-
@keyframes cg-cortina-sobe{ to{ transform:translateY(-100%); visibility:hidden; } }
|
|
145
|
-
@keyframes cg-cortina-mark{ from{ opacity:0; transform:scale(.6) rotate(-16deg); } }
|
|
146
|
-
@keyframes cg-cortina-nome{ from{ opacity:0; transform:translateX(-8px); } }
|
|
147
|
-
@keyframes cg-cortina-nome-dir{ from{ opacity:0; transform:translateX(8px); } }
|
|
148
|
-
@keyframes cg-cortina-tag{ from{ opacity:0; transform:translateY(4px); } }
|
|
149
|
-
html[data-cg-cortina="vista"] .cg-cortina{ display:none; }
|
|
150
|
-
@media (prefers-reduced-motion:reduce){ .cg-cortina{ display:none; } }
|
|
151
|
-
@media print{ .cg-cortina{ display:none; } }
|
|
152
|
-
`;
|