primocode 10.15.2 → 10.16.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.
@@ -0,0 +1,1349 @@
1
+ <!doctype html><html><head><meta charset=utf8><meta name=viewport content="width=device-width,initial-scale=1,viewport-fit=cover"><style>:root{color-scheme:light;box-sizing:border-box;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}html{scroll-padding-top:env(safe-area-inset-top,0px)}body{margin:0;padding:0;font:14px -apple-system,BlinkMacSystemFont,sans-serif;background:#fff;color:#000}img{max-width:100%}[hidden]:not([hidden=until-found i]){display:none!important}</style></head><body>
2
+ <title>Seu gosto de motion · PrimoCode</title>
3
+ <style>
4
+ /* Layout: página de produto da Apple — título centrado, muito respiro, telinhas cinza claras, um azul só pra ação. */
5
+ :root {
6
+ --bg: #ffffff;
7
+ --palco: #ffffff;
8
+ --linha: #d2d2d7;
9
+ --giz: #1d1d1f;
10
+ --apagado: #6e6e73;
11
+ --ambar: #0071e3;
12
+ --ambar-hover: #0077ed;
13
+ --sobre: #ffffff;
14
+ --vermelho: #ff3b30;
15
+ --onda-ceu: #ffffff; --onda-clara: #f2fbff; --onda-media: #9fdcff; --onda-funda: #5ab4ff;
16
+ --sombra-card: 0 2px 6px rgba(0, 40, 90, .06), 0 12px 32px rgba(0, 40, 90, .08);
17
+ --c-brincalhao: var(--giz); --c-premium: var(--giz); --c-corporativo: var(--giz);
18
+ --c-energico: var(--giz); --c-cinema3d: var(--giz); --c-cinetico: var(--giz);
19
+ --f-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Helvetica, Arial, sans-serif;
20
+ --f-poster: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
21
+ --f-serifa: "New York", ui-serif, "Iowan Old Style", Georgia, serif;
22
+ --f-mono: ui-monospace, "SF Mono", Menlo, monospace;
23
+ }
24
+ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
25
+ --bg: #000000; --palco: #1c1c1e; --linha: #38383a; --giz: #f5f5f7; --apagado: #98989d;
26
+ --ambar: #2997ff; --ambar-hover: #47a6ff; --sobre: #ffffff; --vermelho: #ff453a; color-scheme: dark;
27
+ --onda-ceu: #03070f; --onda-clara: #b9ecff; --onda-media: #2f8fe8; --onda-funda: #5fd0ff;
28
+ --sombra-card: 0 2px 6px rgba(0, 0, 0, .4), 0 12px 32px rgba(0, 0, 0, .35); } }
29
+ :root[data-theme="dark"] {
30
+ --bg: #000000; --palco: #1c1c1e; --linha: #38383a; --giz: #f5f5f7; --apagado: #98989d;
31
+ --ambar: #2997ff; --ambar-hover: #47a6ff; --sobre: #ffffff; --vermelho: #ff453a; color-scheme: dark;
32
+ --onda-ceu: #03070f; --onda-clara: #b9ecff; --onda-media: #2f8fe8; --onda-funda: #5fd0ff;
33
+ --sombra-card: 0 2px 6px rgba(0, 0, 0, .4), 0 12px 32px rgba(0, 0, 0, .35); }
34
+ * { box-sizing: border-box; }
35
+ body {
36
+ background: var(--bg); color: var(--giz); font-family: var(--f-display); font-size: 17px; line-height: 1.47; letter-spacing: -.022em;
37
+ -webkit-font-smoothing: antialiased;
38
+ padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px));
39
+ padding-block: 56px 96px;
40
+ }
41
+ #ondas { position: fixed; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none;
42
+ background: linear-gradient(180deg, var(--onda-ceu) 35%, var(--onda-clara) 60%, var(--onda-media)); }
43
+ .palco { box-shadow: var(--sombra-card); }
44
+ .item, .tabela, pre, .passos span.ativo { box-shadow: var(--sombra-card); }
45
+ /* seleção: o círculo vazado aparece no hover; escolhido, fica azul com ✓ */
46
+ .selo { background: var(--bg); box-shadow: inset 0 0 0 1.5px var(--linha), 0 1px 3px rgba(0, 0, 0, .12); }
47
+ .selo::before { content: ""; }
48
+ .card:hover .selo, .card:focus-visible .selo { opacity: 1; transform: scale(1); }
49
+ .card[aria-pressed="true"] .selo { background: var(--ambar); box-shadow: none; }
50
+ .card[aria-pressed="true"] .selo::before { content: "✓"; }
51
+ @media (hover: none) { .selo { opacity: 1; transform: scale(1); } }
52
+ /* catálogo completo */
53
+ .catalogo { margin-top: 40px; display: grid; gap: 24px; }
54
+ .cat-topo { display: grid; gap: 6px; justify-items: center; text-align: center; }
55
+ .cat-topo h2 { margin: 0; font-size: clamp(26px, 3.4vw, 36px); font-weight: 600; letter-spacing: -.025em; }
56
+ .cat-grade { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 20px; align-items: start; }
57
+ .cat-grupo { background: var(--palco); border-radius: 18px; padding: 18px 20px; box-shadow: var(--sombra-card); }
58
+ .cat-grupo h3 { margin: 0 0 8px; font-size: 17px; font-weight: 600; display: flex; justify-content: space-between; align-items: baseline; }
59
+ .cat-grupo ul { list-style: none; margin: 0; padding: 0; }
60
+ .cat-grupo li { display: flex; justify-content: space-between; gap: 12px; padding: 9px 0; border-top: 1px solid var(--linha); font-size: 15px; }
61
+ .cat-grupo li small { color: var(--apagado); font-size: 13px; text-align: right; }
62
+ /* passo 2 e 3: prévia maior, campo no topo, sugestões com + */
63
+ .lista .item { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
64
+ @media (max-width: 700px) { .lista .item { grid-template-columns: minmax(0, 1fr); } }
65
+ .sug { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 18px; }
66
+ .sug button { all: unset; cursor: pointer; font-size: 14px; padding: 7px 14px; border-radius: 980px; color: var(--ambar); background: color-mix(in srgb, var(--ambar) 10%, var(--bg)); transition: background .15s ease, color .15s ease; }
67
+ .sug button::before { content: "+ "; font-weight: 600; }
68
+ .sug button:hover { background: color-mix(in srgb, var(--ambar) 18%, var(--bg)); }
69
+ .sug button:focus-visible { outline: 2px solid var(--ambar); outline-offset: 2px; }
70
+ .sug button[aria-pressed="true"] { background: var(--ambar); color: var(--sobre); }
71
+ .sug button[aria-pressed="true"]::before { content: "✓ "; }
72
+ .rot-chips { margin: 0 0 8px; font-size: 13px; color: var(--apagado); }
73
+ .mini-fila { display: flex; gap: 18px; justify-content: safe center; overflow-x: auto; padding: 8px 4px 18px; margin: -8px -4px -6px; scrollbar-width: thin; }
74
+ .mini-fila .palco { flex: none; width: min(280px, 78vw); border-radius: 18px; }
75
+ #passo2 .carrossel { padding-block: 10px; }
76
+ #passo2 .trilho { width: min(880px, calc(100vw - 32px)); }
77
+ #passo2 .acoes { margin-top: 18px !important; }
78
+ #passo2 .lista { gap: 16px; }
79
+ .lista .item-ops { grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 22px 26px; width: 100%; max-width: 860px; margin: 0 auto; border-radius: 20px; }
80
+ /* compacto: cada linha é o rótulo + quatro pílulas com um ícone pequeno animado */
81
+ .item-ops .grupo { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
82
+ .lista .item-ops h3 { flex: none; width: 128px; margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -.01em; color: var(--giz); }
83
+ .ops { flex: 1; display: flex; flex-wrap: wrap; gap: 10px; }
84
+ .op { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; gap: 10px; padding: 11px 22px 11px 15px; border-radius: 980px;
85
+ background: var(--bg); color: var(--giz); box-shadow: inset 0 0 0 1.5px var(--linha); font-size: 16px; font-weight: 500; white-space: nowrap;
86
+ transition: transform .3s cubic-bezier(.34, 1.56, .64, 1), background .2s ease, box-shadow .2s ease, color .2s ease; }
87
+ .op:hover { transform: translateY(-2px); box-shadow: inset 0 0 0 1.5px var(--ambar); }
88
+ .op:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
89
+ .op[aria-pressed="true"] { background: var(--ambar); color: var(--sobre); box-shadow: none; }
90
+ .menos .op:hover { box-shadow: inset 0 0 0 1.5px var(--vermelho); }
91
+ .menos .op[aria-pressed="true"] { background: var(--vermelho); box-shadow: none; }
92
+ .op .ic { transform: scale(.74); transform-origin: center; margin: -4px -6px; }
93
+ @media (max-width: 560px) { .lista .item-ops h3 { width: 100%; } }
94
+ /* os ícones: cada um faz o movimento que nomeia */
95
+ .ic { position: relative; width: 46px; height: 34px; color: var(--ambar); perspective: 120px; }
96
+ .menos .ic { color: var(--vermelho); }
97
+ .op[aria-pressed="true"] .ic { color: var(--sobre); }
98
+ .ic i { position: absolute; display: block; background: currentColor; }
99
+ .ic-rapido i { left: 6px; height: 4px; border-radius: 2px; animation: icRapido .7s cubic-bezier(.6, 0, .4, 1) infinite; }
100
+ .ic-rapido i:nth-child(1) { top: 5px; width: 30px; } .ic-rapido i:nth-child(2) { top: 15px; width: 22px; animation-delay: .12s; } .ic-rapido i:nth-child(3) { top: 25px; width: 27px; animation-delay: .24s; }
101
+ @keyframes icRapido { 0% { transform: translateX(-10px) scaleX(.4); opacity: 0; } 40% { opacity: 1; } 100% { transform: translateX(12px) scaleX(1); opacity: 0; } }
102
+ .ic-tres_d i { left: 11px; top: 5px; width: 24px; height: 24px; background: none; border: 3px solid currentColor; border-radius: 5px; box-sizing: border-box; animation: ic3d 2.6s linear infinite; }
103
+ @keyframes ic3d { from { transform: rotateX(25deg) rotateY(0deg); } to { transform: rotateX(25deg) rotateY(360deg); } }
104
+ .ic-quique i { left: 17px; top: 0; width: 12px; height: 12px; border-radius: 50%; animation: icQuique .8s infinite; }
105
+ @keyframes icQuique { 0% { transform: translateY(0); animation-timing-function: cubic-bezier(.55, 0, 1, .45); } 45% { transform: translateY(20px) scale(1.3, .7); animation-timing-function: cubic-bezier(0, .55, .45, 1); } 55% { transform: translateY(18px) scale(.9, 1.1); } 100% { transform: translateY(0); } }
106
+ .ic-calmo i { left: 13px; top: 7px; width: 20px; height: 20px; border-radius: 50%; animation: icCalmo 3.2s ease-in-out infinite; }
107
+ @keyframes icCalmo { 0%, 100% { transform: scale(.6); opacity: .45; } 50% { transform: scale(1.1); opacity: 1; } }
108
+ .ic-lento i:nth-child(1) { left: 4px; top: 21px; width: 38px; height: 3px; border-radius: 2px; opacity: .3; }
109
+ .ic-lento i:nth-child(2) { left: 4px; top: 16px; width: 12px; height: 12px; border-radius: 50%; animation: icLento 4s linear infinite; }
110
+ @keyframes icLento { from { transform: translateX(0); } to { transform: translateX(26px); } }
111
+ .ic-exagero i { left: 11px; top: 5px; width: 24px; height: 24px; clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%); animation: icExagero 1s cubic-bezier(.34, 1.56, .64, 1) infinite; }
112
+ @keyframes icExagero { 0%, 100% { transform: scale(.6) rotate(0deg); } 30% { transform: scale(1.45) rotate(-25deg); } 55% { transform: scale(.85) rotate(20deg); } }
113
+ .ic-flash i { left: 9px; top: 3px; width: 28px; height: 28px; border-radius: 6px; animation: icFlash .5s steps(1) infinite; }
114
+ @keyframes icFlash { 0% { opacity: 1; } 50% { opacity: .1; } }
115
+ .ic-tremido i { left: 8px; top: 14px; width: 30px; height: 6px; border-radius: 3px; animation: icTremido .25s steps(4) infinite; }
116
+ @keyframes icTremido { 0% { transform: translate(0, 0); } 25% { transform: translate(-3px, 2px); } 50% { transform: translate(3px, -2px); } 75% { transform: translate(-2px, -1px); } }
117
+ @media (prefers-reduced-motion: reduce) { .ic i { animation: none !important; } }
118
+ /* salvo no PrimoCode */
119
+ .salvo { margin: 0; display: inline-flex; gap: 10px; align-items: center; font-weight: 600; font-size: 17px; }
120
+ .mundo { position: absolute; left: 50%; top: 50%; width: 320px; height: 200px; margin: -100px 0 0 -160px; transform-style: preserve-3d; transform-origin: center; }
121
+ #catGrade { display: grid; grid-template-columns: minmax(0, 1fr); gap: 40px; }
122
+ .cat-sec { display: grid; gap: 16px; }
123
+ .cat-cab { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--linha); }
124
+ .cat-cab h3 { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -.02em; }
125
+ .cat-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 18px; }
126
+ .tile .rotulo b { font-size: 15px; font-weight: 500; }
127
+ .salvo span { width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; background: var(--ambar); color: var(--sobre); font-size: 13px; }
128
+ /* carrossel infinito: colunas de dois motions deslizam por transform; o carrossel vaza até a borda da janela
129
+ e esmaece nas pontas, então o motion que sai vai sumindo em vez de ser cortado pelo bloco */
130
+ body { overflow-x: clip; }
131
+ .carrossel { position: relative; width: 100vw; margin-left: calc(50% - 50vw); padding-block: 18px; overflow: hidden;
132
+ -webkit-mask-image: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent); mask-image: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent); }
133
+ .trilho { position: relative; width: min(1040px, calc(100vw - 32px)); margin: 0 auto; touch-action: pan-y; }
134
+ .trilho:focus-visible { outline: none; }
135
+ .trilho:focus-visible .coluna.vis { outline: 2px solid var(--ambar); outline-offset: 6px; border-radius: 22px; }
136
+ .coluna { position: absolute; left: 0; top: 0; display: grid; gap: 20px; will-change: transform, opacity;
137
+ transition: transform .9s cubic-bezier(.65, 0, .25, 1), opacity .9s cubic-bezier(.65, 0, .25, 1), filter .9s ease; }
138
+ .coluna.salta { transition: none; }
139
+ .coluna.longe { pointer-events: none; }
140
+ @media (prefers-reduced-motion: reduce) { .coluna { transition: opacity .2s linear; } }
141
+ .seta { all: unset; box-sizing: border-box; position: absolute; top: calc(50% - 24px); z-index: 4; width: 48px; height: 48px; border-radius: 50%; cursor: pointer;
142
+ display: grid; place-items: center; color: var(--giz); background: color-mix(in srgb, var(--palco) 80%, transparent);
143
+ -webkit-backdrop-filter: blur(14px) saturate(1.5); backdrop-filter: blur(14px) saturate(1.5);
144
+ box-shadow: var(--sombra-card), inset 0 0 0 1px var(--linha); transition: opacity .2s ease, transform .2s ease; }
145
+ .seta svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
146
+ .seta:hover { transform: scale(1.08); }
147
+ .seta:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
148
+ .seta-volta { left: max(16px, calc(50% - 560px)); } .seta-vai { right: max(16px, calc(50% - 560px)); }
149
+ .rotulo .tipo { font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--ambar); }
150
+ .wrap { max-width: 1040px; margin: 0 auto; display: grid; gap: 48px; }
151
+ header { display: grid; gap: 12px; justify-items: center; text-align: center; }
152
+ .passos { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; font-size: 12px; color: var(--apagado); letter-spacing: 0; }
153
+ .passos span { padding: 4px 12px; border-radius: 999px; }
154
+ .passos span.ativo { color: var(--giz); background: var(--palco); font-weight: 600; }
155
+ h1 { font-size: clamp(32px, 5.4vw, 56px); line-height: 1.07; font-weight: 600; letter-spacing: -.028em; margin: 8px 0 0; text-wrap: balance; max-width: 18ch; }
156
+ .sub { color: var(--apagado); margin: 0; max-width: 46ch; font-size: clamp(17px, 2vw, 21px); line-height: 1.38; }
157
+
158
+ .grade { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
159
+ @media (max-width: 900px) { .grade { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
160
+ @media (max-width: 600px) { .grade { grid-template-columns: minmax(0, 1fr); } }
161
+
162
+ .card { all: unset; box-sizing: border-box; display: grid; gap: 14px; cursor: pointer; border-radius: 22px; position: relative; transition: transform .3s ease; }
163
+ .card:hover { transform: scale(1.012); }
164
+ .card:focus-visible { outline: 2px solid var(--ambar); outline-offset: 4px; }
165
+ .card[aria-pressed="true"] .palco { box-shadow: 0 0 0 2px var(--ambar); }
166
+ .selo { position: absolute; top: 12px; right: 12px; z-index: 3; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
167
+ font-size: 0; color: var(--sobre); background: var(--ambar); opacity: 0; transform: scale(.6); transition: opacity .2s ease, transform .25s ease; }
168
+ .selo::before { content: "✓"; font-size: 14px; font-weight: 600; }
169
+ .card[aria-pressed="true"] .selo { opacity: 1; transform: scale(1); }
170
+ .palco { position: relative; aspect-ratio: 16 / 10; max-width: 100%; background: var(--palco); border-radius: 18px; overflow: hidden; perspective: 900px; display: grid; place-items: center; transition: box-shadow .2s ease; }
171
+ .cena { position: relative; width: 100%; height: 100%; display: grid; place-items: center; transform-style: preserve-3d; }
172
+ .rotulo { display: grid; gap: 2px; padding: 0 4px; text-align: center; }
173
+ .rotulo b { font-size: 19px; font-weight: 600; color: var(--giz); }
174
+ .rotulo small { color: var(--apagado); font-size: 14px; letter-spacing: -.01em; }
175
+
176
+ /* peças das cenas */
177
+ .palavra { font-weight: 700; font-size: clamp(30px, 4.2vw, 44px); letter-spacing: -.03em; color: var(--cor); will-change: transform, opacity, filter; }
178
+ .bola { position: absolute; width: 30px; height: 30px; border-radius: 50%; background: var(--cor); bottom: 16%; left: 20%; }
179
+ .cartao { position: absolute; width: 30%; aspect-ratio: 3 / 2; border: 2px solid var(--cor); border-radius: 12px; }
180
+ .letras { display: flex; gap: .02em; font-family: var(--f-poster); font-weight: 800; letter-spacing: -.04em; font-size: clamp(38px, 5.4vw, 58px); color: var(--cor); }
181
+ .letras span { display: inline-block; will-change: transform; }
182
+ .faisca { position: absolute; width: 18px; height: 2px; background: var(--cor); border-radius: 1px; opacity: 0; }
183
+
184
+ .acoes { display: flex; gap: 20px; align-items: center; justify-content: center; flex-wrap: wrap; }
185
+ .btn { all: unset; cursor: pointer; font-size: 17px; font-weight: 400; padding: 12px 24px; border-radius: 980px; background: var(--ambar); color: var(--sobre); transition: background .2s ease, opacity .2s ease; }
186
+ .btn:hover { background: var(--ambar-hover); }
187
+ .btn:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
188
+ .btn[disabled] { opacity: .3; cursor: not-allowed; }
189
+ /* botão grande, sem brilho; o Continuar do passo 1 só aparece depois da primeira escolha */
190
+ .btn-grande { font-size: 19px; font-weight: 600; padding: 17px 42px; transition: background .2s ease, transform .2s ease, opacity .35s ease; }
191
+ .btn-grande:hover { transform: translateY(-1px); }
192
+ #ir2 { transition: background .2s ease, opacity .4s ease, transform .4s cubic-bezier(.34, 1.56, .64, 1); }
193
+ #ir2[disabled] { opacity: 0; transform: translateY(10px) scale(.96); pointer-events: none; }
194
+ .link { all: unset; cursor: pointer; color: var(--ambar); font-size: 17px; }
195
+ .link:hover { text-decoration: underline; }
196
+ /* botão secundário de vidro: legível em cima da onda nos dois temas */
197
+ .btn-vidro { all: unset; cursor: pointer; font-size: 17px; padding: 16px 30px; border-radius: 980px; color: var(--giz);
198
+ background: color-mix(in srgb, var(--palco) 72%, transparent); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
199
+ box-shadow: inset 0 0 0 1px var(--linha); transition: background .2s ease; }
200
+ .btn-vidro:hover { background: var(--palco); }
201
+ .btn-vidro:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
202
+ .link:focus-visible { outline: 2px solid var(--ambar); outline-offset: 2px; }
203
+ .conta { font-size: 14px; color: var(--apagado); font-variant-numeric: tabular-nums; }
204
+
205
+ /* passo 2 e 3 */
206
+ .lista { display: grid; gap: 20px; }
207
+ .item { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 32px; align-items: center; padding: 24px; background: var(--palco); border-radius: 22px; }
208
+ .item .palco { background: var(--bg); }
209
+ @media (max-width: 700px) { .item { grid-template-columns: minmax(0, 1fr); gap: 20px; } }
210
+ .item h3 { margin: 0 0 14px; font-size: 24px; font-weight: 600; letter-spacing: -.02em; color: var(--giz); }
211
+ .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
212
+ .chip { all: unset; cursor: pointer; font-size: 14px; padding: 8px 16px; border-radius: 980px; background: var(--bg); color: var(--giz); box-shadow: inset 0 0 0 1px var(--linha); transition: background .15s ease, box-shadow .15s ease, color .15s ease; }
213
+ .chip:hover { box-shadow: inset 0 0 0 1px var(--apagado); }
214
+ .chip:focus-visible { outline: 2px solid var(--ambar); outline-offset: 2px; }
215
+ .chip[aria-pressed="true"] { background: var(--ambar); color: var(--sobre); box-shadow: none; }
216
+ .nao .chip[aria-pressed="true"] { background: var(--vermelho); }
217
+ .nao h3 { color: var(--giz) !important; }
218
+ textarea { width: 100%; min-height: 72px; resize: vertical; background: var(--bg); color: var(--giz); border: 1px solid var(--linha); border-radius: 12px; padding: 12px 14px; font: inherit; font-size: 15px; }
219
+ textarea:focus-visible { outline: none; border-color: var(--ambar); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ambar) 25%, transparent); }
220
+
221
+ /* ficha final */
222
+ .ficha { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 32px; }
223
+ @media (max-width: 800px) { .ficha { grid-template-columns: minmax(0, 1fr); } }
224
+ .ficha .acoes { justify-content: flex-start; }
225
+ .resumo { font-size: clamp(21px, 2.6vw, 28px); line-height: 1.3; font-weight: 600; letter-spacing: -.02em; margin: 0; max-width: 32ch; }
226
+ .tabela { font-size: 15px; background: var(--palco); border-radius: 18px; padding: 8px 20px; margin: 0; }
227
+ .tabela div { display: flex; justify-content: space-between; gap: 16px; padding: 13px 0; border-bottom: 1px solid var(--linha); }
228
+ .tabela div:last-child { border-bottom: 0; }
229
+ .tabela dt { color: var(--apagado); }
230
+ .tabela dd { margin: 0; text-align: right; color: var(--giz); font-weight: 500; font-variant-numeric: tabular-nums; }
231
+ .barra { height: 4px; background: var(--linha); border-radius: 2px; overflow: hidden; margin-top: 8px; }
232
+ .barra i { display: block; height: 100%; background: var(--ambar); border-radius: 2px; transition: width .9s cubic-bezier(.4,0,.2,1); }
233
+ pre { margin: 0; overflow-x: auto; background: var(--palco); border-radius: 18px; padding: 16px 20px; font-family: var(--f-mono); font-size: 12px; color: var(--apagado); max-height: 240px; letter-spacing: 0; }
234
+ /* tela final: só a frase, o check e o botão */
235
+ .fim { display: grid; justify-items: center; gap: 22px; text-align: center; margin-top: -8px; }
236
+ .fim-check svg { width: 96px; height: 96px; fill: none; stroke: var(--ambar); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; overflow: visible; }
237
+ .fim-check circle { stroke-dasharray: 145; stroke-dashoffset: 145; }
238
+ .fim-check path { stroke-width: 4; stroke-dasharray: 34; stroke-dashoffset: 34; }
239
+ #passo4:not([hidden]) .fim-check { animation: fimPula .7s cubic-bezier(.34, 1.56, .64, 1) .55s both; }
240
+ #passo4:not([hidden]) .fim-check circle { animation: fimTraco .6s cubic-bezier(.65, 0, .35, 1) forwards; }
241
+ #passo4:not([hidden]) .fim-check path { animation: fimTraco .35s cubic-bezier(.65, 0, .35, 1) .5s forwards; }
242
+ @keyframes fimTraco { to { stroke-dashoffset: 0; } }
243
+ @keyframes fimPula { 0% { transform: scale(1); } 40% { transform: scale(1.16); } 100% { transform: scale(1); } }
244
+ @media (prefers-reduced-motion: reduce) { .fim-check circle, .fim-check path { stroke-dashoffset: 0; animation: none !important; } .fim-check { animation: none !important; } }
245
+ .fim-sub { margin: 0; color: var(--apagado); font-size: clamp(17px, 2vw, 21px); }
246
+ .pular { font-size: 14px; color: var(--apagado); }
247
+ .erro { margin: 14px auto 0; max-width: 46ch; text-align: center; color: var(--vermelho); font-size: 15px; }
248
+ .nota { color: var(--apagado); font-size: 14px; margin: 0; max-width: 60ch; }
249
+ code { font-family: var(--f-mono); font-size: .92em; }
250
+ </style>
251
+
252
+ <canvas id="ondas" aria-hidden="true"></canvas>
253
+ <div class="wrap">
254
+ <header>
255
+ <h1 id="titulo">Qual movimento tem a sua cara?</h1>
256
+ </header>
257
+
258
+ <section id="passo1">
259
+ <div class="carrossel" id="carrossel">
260
+ <button class="seta seta-volta" id="setaVolta" aria-label="Voltar" aria-controls="grade"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg></button>
261
+ <div class="trilho" id="grade" tabindex="0" aria-label="Todos os motions"></div>
262
+ <button class="seta seta-vai" id="setaVai" aria-label="Avançar" aria-controls="grade"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg></button>
263
+ </div>
264
+ <div class="acoes" style="margin-top:36px"><button class="btn btn-grande" id="ir2" disabled>Continuar</button></div>
265
+ <div class="acoes" style="margin-top:14px"><button class="link pular" id="pular">pular por agora</button></div>
266
+ </section>
267
+
268
+ <section id="passo2" hidden>
269
+ <div class="lista" id="lista2"></div>
270
+ <div class="acoes" style="margin-top:28px"><button class="btn btn-grande" id="ir3">Continuar</button><button class="btn-vidro" id="volta1">Voltar</button></div>
271
+ <p class="erro" id="erro" role="alert" hidden></p>
272
+ </section>
273
+
274
+ <section id="passo3" hidden>
275
+ <div class="lista">
276
+ <div class="item nao" style="--cor:var(--giz)">
277
+ <div class="palco" id="palcoNao"></div>
278
+ <div>
279
+ <h3>O que você NÃO quer ver?</h3>
280
+ <textarea id="naoTexto" placeholder="algo que te incomoda em motion? (opcional)"></textarea>
281
+ <div class="sug" id="sugNao"></div>
282
+ <p class="rot-chips">Marque o que você não quer</p>
283
+ <div class="chips" id="chipsNao"></div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ <div class="acoes" style="margin-top:22px"><button class="btn" id="salvar">Salvar meu gosto</button><button class="link" id="volta2">voltar</button></div>
288
+ </section>
289
+
290
+ <section id="passo4" hidden>
291
+ <div class="fim">
292
+ <div class="fim-check" aria-hidden="true"><svg viewBox="0 0 52 52"><circle cx="26" cy="26" r="23"/><path d="M15.5 27l7 7 14-15"/></svg></div>
293
+ <p class="fim-sub" id="fimSub" aria-live="polite">Tudo salvo no seu PrimoCode.</p>
294
+ <button class="btn btn-grande" id="finalizar">Voltar pro PrimoCode</button>
295
+ </div>
296
+ </section>
297
+ </div>
298
+
299
+ <script>
300
+ const TOKEN = '__TOKEN__';
301
+ window.GOSTO = __DADOS__; // o servidor manda os estilos que ele conhece; a tela só mostra esses
302
+ const ESTILOS = [
303
+ { id: 'brincalhao', nome: 'Brincalhão', frase: 'quica e passa do ponto', ritmo: 'rapido', cameras: ['pulso','orbita'], trocas: ['giro','encolhe','abre'], tipografias: ['arredondada','manuscrita'], overshoot: .18 },
304
+ { id: 'premium', nome: 'Premium', frase: 'lento, liso, elegante', ritmo: 'lento', cameras: ['cinema','deriva'], trocas: ['desfoque','dissolve','cortina'], tipografias: ['serifa_fina','editorial'], overshoot: 0 },
305
+ { id: 'corporativo', nome: 'Direto', frase: 'empurra e corta seco', ritmo: 'medio', cameras: ['trilho','push'], trocas: ['empurra','persiana','corte'], tipografias: ['geometrica','tecnica'], overshoot: .04 },
306
+ { id: 'energico', nome: 'Enérgico', frase: 'chicote, punch no beat', ritmo: 'rapido', cameras: ['chicote','pulso'], trocas: ['chicote','tremor','zoom','corte'], tipografias: ['pesada','poster'], overshoot: .25 },
307
+ { id: 'cinema3d', nome: 'Cinema 3D', frase: 'gira em profundidade', ritmo: 'medio', cameras: ['orbita','cinema','push'], trocas: ['giro','zoom','empurra'], tipografias: ['poster','editorial'], overshoot: .1 },
308
+ { id: 'cinetico', nome: 'Letra que dança', frase: 'cada letra entra sozinha', ritmo: 'rapido', cameras: ['pulso','push'], trocas: ['corte','chicote','encolhe'], tipografias: ['poster','pesada','tecnica'], overshoot: .15 },
309
+ ];
310
+ const GOSTEI = [['rapido','rápido'],['tres_d','3D'],['quique','com quique'],['calmo','calmo']];
311
+ const NAO_QUERO = [['lento','devagar'],['exagero','exagerado'],['flash','flash'],['tremido','tremido']];
312
+ const RITMO_NOME = { rapido: 'rápido', medio: 'médio', lento: 'lento' };
313
+ const SUGESTOES = {
314
+ brincalhao: ['o quique no fim', 'a bolinha amassando', 'parece desenho animado', 'leve e divertido'],
315
+ premium: ['a entrada lenta', 'o desfoque saindo', 'parece marca de luxo', 'a letra com serifa'],
316
+ direto: ['sem enrolação', 'o corte seco', 'tudo alinhado', 'parece apresentação de empresa'],
317
+ energico: ['o soco na batida', 'a letra inclinada', 'muita energia', 'parece trailer'],
318
+ cinema3d: ['a profundidade', 'o giro do cartão', 'parece filme', 'a palavra vindo do fundo'],
319
+ cinetico: ['cada letra sozinha', 'o giro do O', 'ritmo de música', 'letra grande e pesada'],
320
+ };
321
+ const SUG_NAO = ['elemento colado na borda', 'música fraca', 'efeito demais', 'texto difícil de ler', 'todo vídeo igual'];
322
+
323
+ /* pílulas "+": um toque põe a frase no campo, outro tira; o texto continua editável */
324
+ function sugestoes(frases, ta, aoMudar) {
325
+ return el('div', { class: 'sug' }, frases.map((f) => {
326
+ const b = el('button', { 'aria-pressed': 'false', text: f });
327
+ b.addEventListener('click', () => {
328
+ const on = b.getAttribute('aria-pressed') !== 'true';
329
+ b.setAttribute('aria-pressed', String(on));
330
+ const partes = ta.value.split(',').map((s) => s.trim()).filter((s) => s && s !== f);
331
+ if (on) partes.push(f);
332
+ ta.value = partes.join(', ').slice(0, 280);
333
+ aoMudar(ta.value);
334
+ });
335
+ return b;
336
+ }));
337
+ }
338
+ const parar = matchMedia('(prefers-reduced-motion: reduce)').matches;
339
+
340
+ const estado = { escolhidos: new Map(), nao: new Set(), naoTexto: '', extras: new Set() };
341
+ const $ = (s) => document.querySelector(s);
342
+ const el = (tag, attrs = {}, kids = []) => { const e = document.createElement(tag); for (const [k, v] of Object.entries(attrs)) { if (k === 'text') e.textContent = v; else if (k === 'style') e.style.cssText = v; else e.setAttribute(k, v); } kids.forEach((k) => e.appendChild(k)); return e; };
343
+
344
+ /* ── as cenas ─────────────────────────────────────────────────────────────
345
+ Cada telinha é um "mundo" de 320×200 px escalado pro tamanho do card, então as
346
+ distâncias valem igual no celular e no desktop. Easing vai em cada keyframe
347
+ (o easing geral da animação distorceria a linha do tempo inteira). */
348
+ const OVER = 'cubic-bezier(.34,1.56,.64,1)', LISO = 'cubic-bezier(.4,0,.2,1)', MD3 = 'cubic-bezier(.2,0,0,1)',
349
+ CAI = 'cubic-bezier(.55,0,1,.45)', SOBE = 'cubic-bezier(0,.55,.45,1)', CHICOTE = 'cubic-bezier(.75,0,.15,1)';
350
+ const loop = (n, kf, opt) => n.animate(kf, { iterations: Infinity, ...opt });
351
+ const TINTA = 'var(--cor)', VEU = 'color-mix(in srgb, var(--cor) 9%, transparent)';
352
+ const T = (x = 0, y = 0, r = 0, sx = 1, sy = sx) => `translate(${x}px,${y}px) rotate(${r}deg) scale(${sx},${sy})`;
353
+
354
+ function mundo(palco) {
355
+ const m = el('div', { class: 'mundo' });
356
+ palco.appendChild(m);
357
+ const ajustar = () => { m.style.scale = String((palco.clientWidth || 320) / 320); };
358
+ ajustar();
359
+ try { new ResizeObserver(ajustar).observe(palco); } catch { /* sem ResizeObserver: fica no tamanho inicial */ }
360
+ return m;
361
+ }
362
+ const peca = (pai, estilo, texto) => {
363
+ const e = el('div', texto == null ? { style: 'position:absolute;' + estilo } : { style: 'position:absolute;' + estilo, text: texto });
364
+ pai.appendChild(e); return e;
365
+ };
366
+ const palavra = (pai, estilo, texto = 'PRIMO') => peca(pai, `font-family:var(--f-display);font-weight:800;letter-spacing:-.04em;color:${TINTA};white-space:nowrap;line-height:1;` + estilo, texto);
367
+ function letras(pai, estilo, texto = 'PRIMO') {
368
+ const w = peca(pai, `display:flex;font-family:var(--f-display);font-weight:800;letter-spacing:-.04em;color:${TINTA};line-height:1;white-space:pre;` + estilo);
369
+ return texto.split('').map((ch) => { const s = el('span', { text: ch, style: 'display:inline-block;will-change:transform' }); w.appendChild(s); return s; });
370
+ }
371
+
372
+ function montarCena(id, palco) {
373
+ const m = mundo(palco), A = [];
374
+ if (parar) {
375
+ const p = palavra(m, 'left:0;right:0;top:78px;text-align:center;font-size:44px');
376
+ A.push(loop(p, [{ opacity: .35 }, { opacity: 1 }], { duration: 2400, direction: 'alternate' }));
377
+ return A;
378
+ }
379
+ const CENAS = { ...NOVAS_CENAS, brincalhao: cenaBrincalhao, premium: cenaPremium, corporativo: cenaDireto, energico: cenaEnergico, cinema3d: cenaCinema3d, cinetico: cenaCinetico, nao: cenaNao };
380
+ (CENAS[id] || cenaPremium)(m, A);
381
+ return A;
382
+ }
383
+
384
+ /* Brincalhão: a bolinha cai, quica três vezes, estoura em confete e vira a palavra. */
385
+ function cenaBrincalhao(m, A) {
386
+ const D = 3200;
387
+ const bola = peca(m, `left:146px;top:-34px;width:28px;height:28px;border-radius:50%;background:${TINTA}`);
388
+ A.push(loop(bola, [
389
+ { transform: T(0, 0), opacity: 1, easing: CAI },
390
+ { transform: T(0, 150, 0, 1.3, .65), offset: .14, easing: SOBE },
391
+ { transform: T(0, 80, 0, .85, 1.15), offset: .24, easing: CAI },
392
+ { transform: T(0, 150, 0, 1.2, .75), offset: .33, easing: SOBE },
393
+ { transform: T(0, 118, 0, .95, 1.05), offset: .4, easing: CAI },
394
+ { transform: T(0, 150, 0, 1.1, .85), offset: .46 },
395
+ { transform: T(0, 140, 0, 1.6), opacity: 1, offset: .5 },
396
+ { transform: T(0, 140, 0, 0), opacity: 0, offset: .53 },
397
+ { transform: T(0, 0, 0, 0), opacity: 0 }], { duration: D }));
398
+ for (let i = 0; i < 10; i++) {
399
+ const a = (i / 10) * Math.PI * 2, r = 70 + (i % 3) * 18, t = i % 2 ? 6 : 9;
400
+ const c = peca(m, `left:156px;top:118px;width:${t}px;height:${t}px;border-radius:${i % 3 ? '50%' : '2px'};background:${TINTA};opacity:0`);
401
+ A.push(loop(c, [
402
+ { transform: T(0, 0, 0, 0), opacity: 0 },
403
+ { transform: T(0, 0, 0, 0), opacity: 0, offset: .5 },
404
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .51, easing: 'cubic-bezier(.1,.8,.3,1)' },
405
+ { transform: T(Math.cos(a) * r, Math.sin(a) * r * .7 + 20, i * 50, .6), opacity: 0, offset: .74 },
406
+ { transform: T(0, 0, 0, 0), opacity: 0 }], { duration: D }));
407
+ }
408
+ letras(m, 'left:0;right:0;top:66px;justify-content:center;font-size:52px').forEach((s, i) => {
409
+ const t = .5 + i * .028, rot = [-14, 10, -6, 12, -10][i];
410
+ A.push(loop(s, [
411
+ { transform: T(0, 40, 0, 0) },
412
+ { transform: T(0, 40, 0, 0), offset: t, easing: OVER },
413
+ { transform: T(0, -18, rot, 1.3), offset: t + .07 },
414
+ { transform: T(0, 4, -rot / 3, .94), offset: t + .12 },
415
+ { transform: T(0, 0, 0, 1), offset: t + .17 },
416
+ { transform: T(0, 0, 0, 1), offset: .86, easing: 'ease-out' },
417
+ { transform: T(0, -6, 0, 1.1), offset: .9, easing: 'ease-in' },
418
+ { transform: T(0, 30, 0, 0), offset: .97 },
419
+ { transform: T(0, 30, 0, 0) }], { duration: D }));
420
+ });
421
+ const card = peca(m, `left:238px;top:22px;width:46px;height:32px;border-radius:9px;border:2.5px solid ${TINTA}`);
422
+ A.push(loop(card, [
423
+ { transform: T(0, 0, -30, 0) }, { transform: T(0, 0, -30, 0), offset: .62, easing: OVER },
424
+ { transform: T(0, 0, 14, 1.25), offset: .7 }, { transform: T(0, 0, -6, 1), offset: .76 },
425
+ { transform: T(0, 0, 3, 1), offset: .82 }, { transform: T(0, 0, 0, 1), offset: .88, easing: 'ease-in' },
426
+ { transform: T(0, 0, 30, 0), offset: .96 }, { transform: T(0, 0, 30, 0) }], { duration: D }));
427
+ }
428
+
429
+ /* Premium: a câmera recua devagar, a palavra sai do desfoque apertando o espaçamento e um brilho corre por ela. */
430
+ function cenaPremium(m, A) {
431
+ const D = 4400;
432
+ A.push(loop(m, [{ transform: 'scale(1.14)' }, { transform: 'scale(1)' }], { duration: D, easing: LISO }));
433
+ const fonte = 'left:0;right:0;top:56px;text-align:center;font-family:var(--f-serifa);font-style:italic;font-weight:400;font-size:60px;letter-spacing:-.01em;';
434
+ const p = palavra(m, fonte, 'Primo');
435
+ A.push(loop(p, [
436
+ { opacity: 0, filter: 'blur(16px)', letterSpacing: '.35em', transform: 'translateY(10px)', easing: LISO },
437
+ { opacity: 1, filter: 'blur(0px)', letterSpacing: '-.01em', transform: 'translateY(0)', offset: .42 },
438
+ { opacity: 1, filter: 'blur(0px)', letterSpacing: '-.01em', transform: 'translateY(0)', offset: .84, easing: LISO },
439
+ { opacity: 0, filter: 'blur(12px)', letterSpacing: '.1em', transform: 'translateY(-6px)' }], { duration: D }));
440
+ const brilho = palavra(m, fonte + 'color:transparent;background:linear-gradient(105deg,transparent 42%,rgba(255,255,255,.95) 50%,transparent 58%);background-size:300% 100%;-webkit-background-clip:text;background-clip:text', 'Primo');
441
+ A.push(loop(brilho, [
442
+ { backgroundPosition: '100% 0', opacity: 0 }, { backgroundPosition: '100% 0', opacity: 1, offset: .46, easing: LISO },
443
+ { backgroundPosition: '0% 0', opacity: 1, offset: .74 }, { backgroundPosition: '0% 0', opacity: 0, offset: .8 }, { backgroundPosition: '0% 0', opacity: 0 }], { duration: D }));
444
+ const linha = peca(m, `left:110px;top:134px;width:100px;height:1px;background:${TINTA}`);
445
+ A.push(loop(linha, [
446
+ { transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .3, easing: LISO }, { transform: 'scaleX(1)', offset: .56 },
447
+ { transform: 'scaleX(1)', offset: .84, easing: LISO }, { transform: 'scaleX(0)', opacity: 0 }], { duration: D }));
448
+ const tag = peca(m, `left:0;right:0;top:148px;text-align:center;font-family:var(--f-mono);font-size:9px;letter-spacing:.6em;color:${TINTA}`, 'ESTÚDIO · MMXXVI');
449
+ A.push(loop(tag, [
450
+ { opacity: 0, letterSpacing: '1em' }, { opacity: 0, letterSpacing: '1em', offset: .46, easing: LISO },
451
+ { opacity: .7, letterSpacing: '.6em', offset: .66 }, { opacity: .7, letterSpacing: '.6em', offset: .84 }, { opacity: 0, letterSpacing: '.6em' }], { duration: D }));
452
+ }
453
+
454
+ /* Direto: o título sobe por máscara, as barras entram empurrando, o anel enche e tudo sai numa cortina seca. */
455
+ function cenaDireto(m, A) {
456
+ const D = 2800;
457
+ const g = peca(m, 'left:0;top:0;width:320px;height:200px');
458
+ A.push(loop(g, [
459
+ { clipPath: 'inset(0 0 0 0)' }, { clipPath: 'inset(0 0 0 0)', offset: .82, easing: MD3 },
460
+ { clipPath: 'inset(0 0 0 100%)', offset: .92 }, { clipPath: 'inset(0 0 0 100%)' }], { duration: D }));
461
+ const titulo = palavra(g, 'left:36px;top:26px;font-size:36px');
462
+ A.push(loop(titulo, [
463
+ { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(16px)' }, { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(16px)', offset: .03, easing: MD3 },
464
+ { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)', offset: .18 }, { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)' }], { duration: D }));
465
+ [.7, .45, .86].forEach((v, i) => {
466
+ const y = 80 + i * 32;
467
+ const trilho = peca(g, `left:36px;top:${y}px;width:168px;height:22px;border-radius:7px;background:${VEU}`);
468
+ const cheio = peca(trilho, `left:0;top:0;height:22px;width:${v * 100}%;border-radius:7px;background:${TINTA};transform-origin:left`);
469
+ A.push(loop(trilho, [
470
+ { transform: 'translateX(320px)' }, { transform: 'translateX(320px)', offset: .08 + i * .05, easing: MD3 },
471
+ { transform: 'translateX(0)', offset: .24 + i * .05 }, { transform: 'translateX(0)' }], { duration: D }));
472
+ A.push(loop(cheio, [
473
+ { transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .26 + i * .05, easing: MD3 },
474
+ { transform: 'scaleX(1)', offset: .46 + i * .05 }, { transform: 'scaleX(1)' }], { duration: D }));
475
+ });
476
+ const NS = ['http:', '', 'www.w3.org', '2000', 'svg'].join('/');
477
+ const svg = document.createElementNS(NS, 'svg');
478
+ svg.setAttribute('viewBox', '0 0 64 64');
479
+ svg.style.cssText = 'position:absolute;left:222px;top:86px;width:68px;height:68px;overflow:visible;color:var(--cor)';
480
+ const circ = (attrs) => { const c = document.createElementNS(NS, 'circle'); Object.entries({ cx: 32, cy: 32, r: 26, fill: 'none', stroke: 'currentColor', 'stroke-width': 6, ...attrs }).forEach(([k, v]) => c.setAttribute(k, v)); svg.appendChild(c); return c; };
481
+ circ({ opacity: .12 });
482
+ const arco = circ({ 'stroke-linecap': 'round', 'stroke-dasharray': 163.4, 'stroke-dashoffset': 163.4, transform: 'rotate(-90 32 32)' });
483
+ const txt = document.createElementNS(NS, 'text');
484
+ Object.entries({ x: 32, y: 37, 'text-anchor': 'middle', 'font-size': 14, 'font-weight': 700, fill: 'currentColor', 'font-family': '-apple-system, sans-serif' }).forEach(([k, v]) => txt.setAttribute(k, v));
485
+ txt.textContent = '75%'; svg.appendChild(txt);
486
+ g.appendChild(svg);
487
+ A.push(loop(svg, [
488
+ { transform: 'scale(.5)', opacity: 0 }, { transform: 'scale(.5)', opacity: 0, offset: .16, easing: OVER },
489
+ { transform: 'scale(1)', opacity: 1, offset: .28 }, { transform: 'scale(1)', opacity: 1 }], { duration: D }));
490
+ A.push(loop(arco, [
491
+ { strokeDashoffset: 163.4 }, { strokeDashoffset: 163.4, offset: .28, easing: MD3 },
492
+ { strokeDashoffset: 41 }, { strokeDashoffset: 41 }].map((k, i) => (i === 2 ? { ...k, offset: .56 } : k)), { duration: D }));
493
+ }
494
+
495
+ /* Enérgico: listras correndo, rastros do chicote, a palavra entra inclinada, dois socos de câmera com faísca e um selo carimbado. */
496
+ function cenaEnergico(m, A) {
497
+ const D = 1800;
498
+ const fundo = peca(m, 'left:-60px;top:-40px;width:440px;height:280px;background:repeating-linear-gradient(115deg, color-mix(in srgb, var(--cor) 7%, transparent) 0 3px, transparent 3px 24px)');
499
+ A.push(loop(fundo, [{ transform: 'translateX(0)' }, { transform: 'translateX(-53px)' }], { duration: 360, easing: 'linear' }));
500
+ const cam = peca(m, 'left:0;top:0;width:320px;height:200px');
501
+ A.push(loop(cam, [
502
+ { transform: T(0, 0, 0, 1) }, { transform: T(0, 0, 0, 1), offset: .3 },
503
+ { transform: T(0, 0, -2, 1.14), offset: .33, easing: 'ease-out' }, { transform: T(0, 0, 0, 1), offset: .45 },
504
+ { transform: T(0, 0, 0, 1), offset: .64 }, { transform: T(0, 0, 2, 1.12), offset: .67, easing: 'ease-out' },
505
+ { transform: T(0, 0, 0, 1), offset: .79 }, { transform: T(0, 0, 0, 1) }], { duration: D }));
506
+ [62, 84, 120, 140].forEach((y, i) => {
507
+ const r = peca(cam, `left:0;top:${y}px;width:${120 + i * 30}px;height:${i % 2 ? 2 : 3}px;border-radius:2px;background:${TINTA}`);
508
+ const t = .02 + i * .015;
509
+ A.push(loop(r, [
510
+ { transform: 'translateX(-260px) scaleX(.3)', opacity: 0 }, { transform: 'translateX(-260px) scaleX(.3)', opacity: 0, offset: t, easing: 'ease-out' },
511
+ { transform: 'translateX(120px) scaleX(1)', opacity: .9, offset: t + .12 }, { transform: 'translateX(380px) scaleX(.2)', opacity: 0, offset: t + .22 },
512
+ { transform: 'translateX(380px) scaleX(.2)', opacity: 0 }], { duration: D }));
513
+ });
514
+ const p = palavra(cam, 'left:0;right:0;top:56px;text-align:center;font-size:70px;font-weight:900;font-style:italic;letter-spacing:-.05em');
515
+ A.push(loop(p, [
516
+ { transform: 'translateX(-380px) skewX(-35deg)', easing: CHICOTE },
517
+ { transform: 'translateX(14px) skewX(14deg)', offset: .16, easing: 'ease-out' },
518
+ { transform: 'translateX(0) skewX(0deg)', offset: .24 },
519
+ { transform: 'translateX(0) skewX(0deg)', offset: .86, easing: 'cubic-bezier(.7,0,.9,.3)' },
520
+ { transform: 'translateX(400px) skewX(-30deg)' }], { duration: D }));
521
+ const sub = peca(cam, `left:78px;top:132px;width:164px;height:9px;border-radius:2px;background:${TINTA};transform-origin:left`);
522
+ A.push(loop(sub, [
523
+ { transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .28, easing: OVER }, { transform: 'scaleX(1.12)', offset: .33 },
524
+ { transform: 'scaleX(1)', offset: .4 }, { transform: 'scaleX(1)', offset: .86 }, { transform: 'scaleX(0)', offset: .92 }, { transform: 'scaleX(0)' }], { duration: D }));
525
+ for (let i = 0; i < 8; i++) {
526
+ const x = i < 4 ? 78 : 242, a = (i % 4) * 40 - 60 + (i < 4 ? 180 : 0), b = i < 4 ? .31 : .65;
527
+ const f = peca(cam, `left:${x}px;top:136px;width:16px;height:2.5px;border-radius:2px;background:${TINTA};transform-origin:left center;opacity:0`);
528
+ A.push(loop(f, [
529
+ { opacity: 0, transform: `rotate(${a}deg) translateX(0) scaleX(.2)` }, { opacity: 0, transform: `rotate(${a}deg) translateX(0) scaleX(.2)`, offset: b },
530
+ { opacity: 1, transform: `rotate(${a}deg) translateX(6px) scaleX(1)`, offset: b + .02, easing: 'ease-out' },
531
+ { opacity: 0, transform: `rotate(${a}deg) translateX(30px) scaleX(.3)`, offset: b + .13 }, { opacity: 0, transform: `rotate(${a}deg) translateX(30px) scaleX(.3)` }], { duration: D }));
532
+ }
533
+ const selo = palavra(cam, `left:224px;top:28px;font-size:13px;font-weight:800;letter-spacing:.06em;padding:5px 9px;border-radius:6px;background:${TINTA};color:var(--palco)`, 'NOVO');
534
+ A.push(loop(selo, [
535
+ { transform: T(0, 0, -30, 0), opacity: 0 }, { transform: T(0, 0, -30, 0), opacity: 0, offset: .63 },
536
+ { transform: T(0, 0, -14, 2.2), opacity: .2, offset: .64, easing: 'ease-in' }, { transform: T(0, 0, -12, 1), opacity: 1, offset: .69 },
537
+ { transform: T(0, 0, -12, 1), opacity: 1, offset: .86 }, { transform: T(0, 0, 0, 0), opacity: 0, offset: .92 }, { transform: T(0, 0, 0, 0), opacity: 0 }], { duration: D }));
538
+ }
539
+
540
+ /* Cinema 3D: três planos empilhados em profundidade giram no espaço, a sombra acompanha e a palavra voa do fundo. */
541
+ function cenaCinema3d(m, A) {
542
+ const D = 4200, VAI = 'cubic-bezier(.45,0,.55,1)';
543
+ const chao = peca(m, 'left:96px;top:146px;width:128px;height:20px;border-radius:50%;background:radial-gradient(closest-side, color-mix(in srgb, var(--cor) 24%, transparent), transparent)');
544
+ A.push(loop(chao, [{ transform: 'scaleX(1)', easing: VAI }, { transform: 'scaleX(.72)', offset: .5, easing: VAI }, { transform: 'scaleX(1)' }], { duration: D }));
545
+ const pilha = peca(m, 'left:95px;top:26px;width:130px;height:86px;transform-style:preserve-3d');
546
+ const planos = [0, 1, 2].map((i) => peca(pilha, `left:0;top:0;width:130px;height:86px;box-sizing:border-box;border-radius:14px;border:2px solid ${TINTA};background:${i === 2 ? 'var(--palco)' : VEU};opacity:${i === 0 ? .5 : i === 1 ? .8 : 1};transform:translateZ(${(i - 1) * 34}px)`));
547
+ peca(planos[2], `left:16px;top:18px;width:34px;height:34px;border-radius:50%;border:2.5px solid ${TINTA};box-sizing:border-box`);
548
+ peca(planos[2], `left:62px;top:24px;width:50px;height:6px;border-radius:3px;background:${TINTA}`);
549
+ peca(planos[2], `left:62px;top:38px;width:34px;height:6px;border-radius:3px;background:${TINTA};opacity:.4`);
550
+ peca(planos[2], `left:16px;top:62px;width:96px;height:6px;border-radius:3px;background:${TINTA};opacity:.2`);
551
+ A.push(loop(pilha, [
552
+ { transform: 'translateY(0) rotateX(16deg) rotateY(-38deg)', easing: VAI },
553
+ { transform: 'translateY(-8px) rotateX(6deg) rotateY(38deg)', offset: .5, easing: VAI },
554
+ { transform: 'translateY(0) rotateX(16deg) rotateY(-38deg)' }], { duration: D }));
555
+ const p = palavra(m, 'left:0;right:0;top:140px;text-align:center;font-size:30px;letter-spacing:.02em');
556
+ A.push(loop(p, [
557
+ { transform: 'translateZ(-900px)', opacity: 0, filter: 'blur(8px)' },
558
+ { transform: 'translateZ(-900px)', opacity: 0, filter: 'blur(8px)', offset: .12, easing: 'cubic-bezier(.15,.85,.3,1)' },
559
+ { transform: 'translateZ(50px)', opacity: 1, filter: 'blur(0px)', offset: .4, easing: 'ease-out' },
560
+ { transform: 'translateZ(0px)', opacity: 1, filter: 'blur(0px)', offset: .48 },
561
+ { transform: 'translateZ(0px)', opacity: 1, filter: 'blur(0px)', offset: .86, easing: 'ease-in' },
562
+ { transform: 'translateZ(380px)', opacity: 0, filter: 'blur(6px)' }], { duration: D }));
563
+ }
564
+
565
+ /* Letra que dança: cada letra chega por um caminho, uma onda passa por elas, o O gira e todas se espalham. */
566
+ function cenaCinetico(m, A) {
567
+ const D = 3400;
568
+ const de = [[0, -140, -30, 1], [0, 140, 40, 1], [0, 0, 180, 0], [-200, 0, -40, 1], [220, 0, -200, 1]];
569
+ letras(m, 'left:0;right:0;top:64px;justify-content:center;font-size:66px;font-weight:900').forEach((s, i) => {
570
+ const t0 = i * .045, onda = .38 + i * .045;
571
+ const kf = [
572
+ { transform: T(...de[i]), opacity: 0 },
573
+ { transform: T(...de[i]), opacity: 0, offset: t0, easing: OVER },
574
+ { transform: T(), opacity: 1, offset: t0 + .16 },
575
+ { transform: T(), opacity: 1, offset: onda, easing: 'ease-out' },
576
+ { transform: T(0, -22, 0, .9, 1.2), opacity: 1, offset: onda + .06, easing: 'ease-in' },
577
+ { transform: T(0, 4, 0, 1.12, .86), opacity: 1, offset: onda + .11, easing: 'ease-out' },
578
+ { transform: T(), opacity: 1, offset: onda + .15 },
579
+ ];
580
+ if (i === 4) kf.push({ transform: T(0, 0, 0, 1), opacity: 1, offset: .74, easing: OVER }, { transform: T(0, -10, 360, 1.2), opacity: 1, offset: .82 }, { transform: T(0, 0, 360, 1), opacity: 1, offset: .86 });
581
+ else kf.push({ transform: T(), opacity: 1, offset: .86 });
582
+ const r0 = i === 4 ? 360 : 0;
583
+ kf.push({ transform: T(0, 0, r0, 1), opacity: 1, offset: .88 + i * .01, easing: 'ease-in' },
584
+ { transform: T((i - 2) * 34, -150, r0 + (i - 2) * 40, .6), opacity: 0, offset: .96 + i * .008 },
585
+ { transform: T((i - 2) * 34, -150, r0 + (i - 2) * 40, .6), opacity: 0 });
586
+ A.push(loop(s, kf, { duration: D }));
587
+ });
588
+ }
589
+
590
+ /* O anti-exemplo do passo 3: colado na borda, tremendo, coisa demais na tela. */
591
+ function cenaNao(m, A) {
592
+ const p = palavra(m, 'left:8px;top:8px;font-size:34px');
593
+ const c = peca(m, `left:236px;top:118px;width:110px;height:72px;border-radius:12px;border:2.5px solid ${TINTA}`);
594
+ const b = peca(m, `left:150px;top:120px;width:24px;height:24px;border-radius:50%;background:${TINTA}`);
595
+ [[40, 150], [120, 40], [200, 72], [262, 40], [92, 108]].forEach(([x, y], i) => {
596
+ const q = peca(m, `left:${x}px;top:${y}px;width:${8 + i * 3}px;height:${8 + i * 3}px;border-radius:${i % 2 ? '50%' : '2px'};border:2px solid ${TINTA};opacity:.6`);
597
+ A.push(loop(q, [{ transform: T(0, 0, 0) }, { transform: T(i * 3 - 6, 5 - i * 2, 90 * (i + 1)) }], { duration: 400 + i * 90, direction: 'alternate' }));
598
+ });
599
+ peca(m, 'right:10px;top:10px;width:26px;height:26px;border-radius:50%;display:grid;place-items:center;background:var(--vermelho);color:#fff;font:700 14px/1 var(--f-display)', '✕');
600
+ A.push(loop(p, [{ transform: T(0, 0) }, { transform: T(3, -2, 1) }, { transform: T(-3, 2, -1) }, { transform: T(2, 3) }, { transform: T(0, 0) }], { duration: 180 }));
601
+ A.push(loop(c, [{ transform: T(0, 0, -6) }, { transform: T(0, 0, 8, 1.15) }, { transform: T(0, 0, -6) }], { duration: 500 }));
602
+ A.push(loop(b, [{ transform: T(-60, 0) }, { transform: T(60, -30) }, { transform: T(-40, 20) }, { transform: T(-60, 0) }], { duration: 700 }));
603
+ }
604
+
605
+ /* ── mais 14 motions ─────────────────────────────────────────────────────
606
+ Mesmo mundo de 320×200; ACENTO é o azul da tela, pra cada cena ter duas cores. */
607
+ const ACENTO = 'var(--ambar)';
608
+ const sempre = (o) => [{ ...o }, { ...o }];
609
+
610
+ /* Órbita: três anéis inclinados giram em volta da palavra, cada um com sua lua. */
611
+ function cenaOrbita(m, A) {
612
+ const D = 3600;
613
+ [[68, 0, ACENTO], [68, 60, TINTA], [68, -60, ACENTO]].forEach(([rx, rz, c], i) => {
614
+ const anel = peca(m, `left:65px;top:5px;width:190px;height:190px;border-radius:50%;border:1.5px solid color-mix(in srgb, ${c} 40%, transparent);transform-style:preserve-3d;transform:rotateZ(${rz}deg) rotateX(${rx}deg)`);
615
+ const lua = peca(anel, `left:88px;top:-7px;width:13px;height:13px;border-radius:50%;background:${c};box-shadow:0 0 14px ${c}`);
616
+ A.push(loop(anel, [{ transform: `rotateZ(${rz}deg) rotateX(${rx}deg) rotateZ(0deg)` }, { transform: `rotateZ(${rz}deg) rotateX(${rx}deg) rotateZ(360deg)` }], { duration: 2200 + i * 650, easing: 'linear' }));
617
+ A.push(loop(lua, [{ transform: 'scale(1)' }, { transform: 'scale(1.5)' }], { duration: 900 + i * 200, direction: 'alternate', easing: LISO }));
618
+ });
619
+ const p = palavra(m, 'left:0;right:0;top:80px;text-align:center;font-size:40px');
620
+ A.push(loop(p, [{ transform: 'scale(.5)', opacity: 0, filter: 'blur(6px)' }, { transform: 'scale(1.1)', opacity: 1, filter: 'blur(0px)', offset: .18, easing: OVER },
621
+ { transform: 'scale(1)', opacity: 1, filter: 'blur(0px)', offset: .26 }, { transform: 'scale(1)', opacity: 1, filter: 'blur(0px)', offset: .88, easing: 'ease-in' },
622
+ { transform: 'scale(.5)', opacity: 0, filter: 'blur(6px)' }], { duration: D }));
623
+ }
624
+
625
+ /* Gota: uma gota azul cai, abre três ondas na água e a palavra sobe de dentro delas. */
626
+ function cenaGota(m, A) {
627
+ const D = 3000;
628
+ const g = peca(m, `left:153px;top:-24px;width:14px;height:19px;border-radius:50% 50% 50% 50%/60% 60% 40% 40%;background:${ACENTO}`);
629
+ A.push(loop(g, [{ transform: T(0, 0), opacity: 1, easing: CAI }, { transform: T(0, 142, 0, .8, 1.3), opacity: 1, offset: .22 },
630
+ { transform: T(0, 142, 0, 2.2, 0), opacity: 0, offset: .25 }, { transform: T(0, 0), opacity: 0 }], { duration: D }));
631
+ for (let i = 0; i < 3; i++) {
632
+ const r = peca(m, `left:110px;top:128px;width:100px;height:26px;border-radius:50%;border:2px solid ${ACENTO}`);
633
+ const t = .24 + i * .06;
634
+ A.push(loop(r, [{ transform: 'scale(0)', opacity: 0 }, { transform: 'scale(0)', opacity: 0, offset: t },
635
+ { transform: 'scale(.3)', opacity: 1, offset: t + .01, easing: 'cubic-bezier(.1,.7,.3,1)' }, { transform: 'scale(2.8)', opacity: 0, offset: t + .42 },
636
+ { transform: 'scale(2.8)', opacity: 0 }], { duration: D }));
637
+ }
638
+ const p = palavra(m, 'left:0;right:0;top:66px;text-align:center;font-size:54px;transform-origin:50% 100%');
639
+ A.push(loop(p, [{ transform: 'translateY(46px) scaleY(.1)', opacity: 0, filter: 'blur(6px)' },
640
+ { transform: 'translateY(46px) scaleY(.1)', opacity: 0, filter: 'blur(6px)', offset: .27, easing: OVER },
641
+ { transform: 'translateY(0) scaleY(1)', opacity: 1, filter: 'blur(0px)', offset: .46 },
642
+ { transform: 'translateY(0) scaleY(1)', opacity: 1, filter: 'blur(0px)', offset: .86, easing: 'ease-in' },
643
+ { transform: 'translateY(30px) scaleY(.3)', opacity: 0, filter: 'blur(4px)' }], { duration: D }));
644
+ }
645
+
646
+ /* Glitch: a palavra treme, se parte em fatias coloridas e um raio de varredura corta a tela. */
647
+ function cenaGlitch(m, A) {
648
+ const D = 2200, base = 'left:0;right:0;top:62px;text-align:center;font-size:66px;font-weight:900;';
649
+ const c1 = palavra(m, base + `color:${ACENTO};opacity:.9`), c2 = palavra(m, base + 'color:var(--vermelho);opacity:.8'), p = palavra(m, base);
650
+ const pulo = (dx) => [
651
+ { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)' }, { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .08 },
652
+ { transform: T(dx, 0), clipPath: 'inset(10% 0 55% 0)', offset: .1 }, { transform: T(-dx, 2), clipPath: 'inset(52% 0 18% 0)', offset: .14 },
653
+ { transform: T(dx / 2, 0), clipPath: 'inset(30% 0 40% 0)', offset: .17 }, { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .19 },
654
+ { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .56 }, { transform: T(dx * 1.6, -2), clipPath: 'inset(28% 0 42% 0)', offset: .58 },
655
+ { transform: T(-dx, 1), clipPath: 'inset(68% 0 6% 0)', offset: .62 }, { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .65 },
656
+ { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)' }];
657
+ A.push(loop(c1, pulo(-10), { duration: D }));
658
+ A.push(loop(c2, pulo(10), { duration: D }));
659
+ A.push(loop(p, [{ transform: T(0, 0), opacity: 1 }, { transform: T(0, 0), opacity: 1, offset: .09 }, { transform: T(4, 0, 0, 1.05, .9), opacity: .5, offset: .12 },
660
+ { transform: T(-2, 0), opacity: 1, offset: .17 }, { transform: T(0, 0), opacity: 1, offset: .57 }, { transform: T(-5, 0, 0, 1.08, .92), opacity: .4, offset: .6 },
661
+ { transform: T(0, 0), opacity: 1, offset: .65 }, { transform: T(0, 0), opacity: 1 }], { duration: D }));
662
+ [[48, 5], [96, 3], [132, 8]].forEach(([y, h], i) => {
663
+ const b = peca(m, `left:0;top:${y}px;width:320px;height:${h}px;background:${i === 1 ? 'var(--vermelho)' : ACENTO}`);
664
+ const t = .1 + i * .015, t2 = .58 + i * .015;
665
+ A.push(loop(b, [{ transform: 'translateX(-60px) scaleX(.3)', opacity: 0 }, { transform: 'translateX(-60px) scaleX(.3)', opacity: 0, offset: t },
666
+ { transform: 'translateX(0) scaleX(1)', opacity: .75, offset: t + .015 }, { transform: 'translateX(60px) scaleX(.3)', opacity: 0, offset: t + .04 },
667
+ { transform: 'translateX(60px) scaleX(.3)', opacity: 0, offset: t2 }, { transform: 'translateX(0) scaleX(1)', opacity: .75, offset: t2 + .015 },
668
+ { transform: 'translateX(-60px) scaleX(.3)', opacity: 0, offset: t2 + .04 }, { transform: 'translateX(-60px) scaleX(.3)', opacity: 0 }], { duration: D }));
669
+ });
670
+ }
671
+
672
+ /* Contador: três rolos de número giram até 100%, a barra enche junto e o número dá um soco no fim. */
673
+ function cenaContador(m, A) {
674
+ const D = 3200, h = 64;
675
+ const caixa = peca(m, `left:0;right:0;top:46px;height:${h}px;display:flex;justify-content:center;align-items:center;gap:1px`);
676
+ [1, 10, 10].forEach((alvo, i) => {
677
+ const jan = el('div', { style: `height:${h}px;overflow:hidden` });
678
+ const fita = el('div', { style: `display:flex;flex-direction:column;font:900 58px/${h}px var(--f-display);letter-spacing:-.04em;color:${TINTA};font-variant-numeric:tabular-nums` }, '01234567890'.split('').map((d) => el('span', { text: d })));
679
+ jan.appendChild(fita); caixa.appendChild(jan);
680
+ A.push(loop(fita, [{ transform: 'translateY(0)' }, { transform: 'translateY(0)', offset: .06, easing: 'cubic-bezier(.6,0,.2,1)' },
681
+ { transform: `translateY(${-alvo * h}px)`, offset: .5 + i * .05 }, { transform: `translateY(${-alvo * h}px)`, offset: .9, easing: 'ease-in' },
682
+ { transform: 'translateY(0)' }], { duration: D }));
683
+ });
684
+ caixa.appendChild(el('div', { text: '%', style: `font:800 32px/1 var(--f-display);color:${ACENTO};margin-left:4px` }));
685
+ const trilho = peca(m, `left:70px;top:134px;width:180px;height:6px;border-radius:3px;background:${VEU}`);
686
+ const cheio = peca(trilho, `left:0;top:0;width:180px;height:6px;border-radius:3px;background:${ACENTO};transform-origin:left`);
687
+ A.push(loop(cheio, [{ transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .06, easing: 'cubic-bezier(.6,0,.2,1)' }, { transform: 'scaleX(1)', offset: .6 },
688
+ { transform: 'scaleX(1)', offset: .9, easing: 'ease-in' }, { transform: 'scaleX(0)' }], { duration: D }));
689
+ A.push(loop(caixa, [{ transform: 'scale(1)' }, { transform: 'scale(1)', offset: .6, easing: 'ease-out' }, { transform: 'scale(1.18)', offset: .64, easing: OVER },
690
+ { transform: 'scale(1)', offset: .74 }, { transform: 'scale(1)' }], { duration: D }));
691
+ }
692
+
693
+ /* Cubo: um cubo 3D com uma letra por face gira de quadro em quadro e dá uma cambalhota no fim. */
694
+ function cenaCubo(m, A) {
695
+ const s = 92, D = 4800;
696
+ const chao = peca(m, `left:110px;top:160px;width:100px;height:18px;border-radius:50%;background:radial-gradient(closest-side, color-mix(in srgb, var(--cor) 26%, transparent), transparent)`);
697
+ const cubo = peca(m, `left:114px;top:44px;width:${s}px;height:${s}px;transform-style:preserve-3d`);
698
+ [['rotateY(0deg)', 'P'], ['rotateY(90deg)', 'R'], ['rotateY(180deg)', 'I'], ['rotateY(-90deg)', 'M'], ['rotateX(90deg)', 'O'], ['rotateX(-90deg)', '✦']].forEach(([r, t], i) => {
699
+ const azul = i % 2 === 1;
700
+ peca(cubo, `left:0;top:0;width:${s}px;height:${s}px;box-sizing:border-box;border-radius:14px;border:2px solid ${azul ? ACENTO : TINTA};background:${azul ? ACENTO : 'var(--palco)'};color:${azul ? 'var(--sobre)' : TINTA};display:grid;place-items:center;font:900 46px/1 var(--f-display);transform:${r} translateZ(${s / 2}px);backface-visibility:hidden`, t);
701
+ });
702
+ const k = (y, x = -22) => `rotateX(${x}deg) rotateY(${y}deg)`;
703
+ A.push(loop(cubo, [{ transform: k(0), easing: OVER }, { transform: k(-90), offset: .18 }, { transform: k(-90), offset: .24, easing: OVER },
704
+ { transform: k(-180), offset: .42 }, { transform: k(-180), offset: .48, easing: OVER }, { transform: k(-270), offset: .66 },
705
+ { transform: k(-270), offset: .72, easing: 'cubic-bezier(.6,0,.3,1)' }, { transform: k(-360, 338), offset: .94 }, { transform: k(-360, 338) }], { duration: D }));
706
+ A.push(loop(chao, [{ transform: 'scale(1)' }, { transform: 'scale(1)', offset: .72 }, { transform: 'scale(.6)', offset: .83 }, { transform: 'scale(1)', offset: .94 }, { transform: 'scale(1)' }], { duration: D }));
707
+ }
708
+
709
+ /* Corte ao meio: as duas metades da palavra entram por lados opostos, se abrem e mostram a linha do meio. */
710
+ function cenaSplit(m, A) {
711
+ const D = 3000, base = 'left:0;right:0;top:56px;text-align:center;font-size:80px;font-weight:900;letter-spacing:-.05em;';
712
+ const cima = palavra(m, base + 'clip-path:inset(0 0 50% 0)'), baixo = palavra(m, base + 'clip-path:inset(50% 0 0 0)');
713
+ const meio = palavra(m, `left:0;right:0;top:91px;text-align:center;font-family:var(--f-mono);font-size:12px;font-weight:700;letter-spacing:.6em;color:${ACENTO}`, 'MOTION · 2026');
714
+ const lado = (sx, dy) => [{ transform: T(sx * 340, 0) }, { transform: T(0, 0), offset: .16, easing: MD3 }, { transform: T(0, 0), offset: .26, easing: OVER },
715
+ { transform: T(0, dy), offset: .38 }, { transform: T(0, dy), offset: .76, easing: MD3 }, { transform: T(0, 0), offset: .84, easing: CHICOTE }, { transform: T(-sx * 340, 0) }];
716
+ lado.bind(null);
717
+ A.push(loop(cima, [{ ...lado(-1, -18)[0], easing: CHICOTE }, ...lado(-1, -18).slice(1)], { duration: D }));
718
+ A.push(loop(baixo, [{ ...lado(1, 18)[0], easing: CHICOTE }, ...lado(1, 18).slice(1)], { duration: D }));
719
+ A.push(loop(meio, [{ transform: 'scaleX(0)', opacity: 0 }, { transform: 'scaleX(0)', opacity: 0, offset: .3, easing: MD3 }, { transform: 'scaleX(1)', opacity: 1, offset: .46 },
720
+ { transform: 'scaleX(1)', opacity: 1, offset: .74, easing: MD3 }, { transform: 'scaleX(0)', opacity: 0, offset: .82 }, { transform: 'scaleX(0)', opacity: 0 }], { duration: D }));
721
+ }
722
+
723
+ /* Mosaico: 40 azulejos viram em onda na diagonal, mostram a palavra e viram de novo pra ir embora. */
724
+ function cenaMosaico(m, A) {
725
+ const D = 3400, cols = 8, rows = 5, w = 30, x0 = 40, y0 = 25;
726
+ for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
727
+ const q = peca(m, `left:${x0 + c * w}px;top:${y0 + r * w}px;width:${w - 3}px;height:${w - 3}px;border-radius:6px;background:${(r + c) % 5 ? ACENTO : TINTA}`);
728
+ const d = (r + c) * .025;
729
+ A.push(loop(q, [{ transform: 'rotateY(90deg) scale(.4)', opacity: 0 }, { transform: 'rotateY(90deg) scale(.4)', opacity: 0, offset: .04 + d, easing: OVER },
730
+ { transform: 'rotateY(0deg) scale(1)', opacity: 1, offset: .14 + d }, { transform: 'rotateY(0deg) scale(1)', opacity: 1, offset: .5 + d, easing: 'ease-in' },
731
+ { transform: 'rotateY(-90deg) scale(.4)', opacity: 0, offset: .6 + d }, { transform: 'rotateY(-90deg) scale(.4)', opacity: 0 }], { duration: D }));
732
+ }
733
+ const claro = palavra(m, 'left:0;right:0;top:72px;text-align:center;font-size:56px;color:var(--sobre)');
734
+ const escuro = palavra(m, 'left:0;right:0;top:72px;text-align:center;font-size:56px');
735
+ A.push(loop(claro, [{ opacity: 0, transform: 'scale(.8)' }, { opacity: 0, transform: 'scale(.8)', offset: .3, easing: OVER }, { opacity: 1, transform: 'scale(1)', offset: .4 },
736
+ { opacity: 1, transform: 'scale(1)', offset: .58 }, { opacity: 0, transform: 'scale(1)', offset: .7 }, { opacity: 0, transform: 'scale(1)' }], { duration: D }));
737
+ A.push(loop(escuro, [{ opacity: 0, transform: 'scale(1)' }, { opacity: 0, transform: 'scale(1)', offset: .58 }, { opacity: 1, transform: 'scale(1)', offset: .7 },
738
+ { opacity: 1, transform: 'scale(1)', offset: .88, easing: 'ease-in' }, { opacity: 0, transform: 'scale(1.3)' }], { duration: D }));
739
+ }
740
+
741
+ /* Espiral: dezesseis pontos giram em espiral até o centro e explodem na palavra. */
742
+ function cenaEspiral(m, A) {
743
+ const D = 3200;
744
+ for (let i = 0; i < 16; i++) {
745
+ const a = i * 22.5, d = peca(m, `left:156px;top:96px;width:8px;height:8px;border-radius:50%;background:${i % 2 ? ACENTO : TINTA}`);
746
+ A.push(loop(d, [{ transform: `rotate(${a}deg) translateX(150px) scale(1)`, opacity: 0 },
747
+ { transform: `rotate(${a}deg) translateX(150px) scale(1)`, opacity: 1, offset: .05, easing: 'cubic-bezier(.5,0,.8,.2)' },
748
+ { transform: `rotate(${a + 540}deg) translateX(0px) scale(.4)`, opacity: 1, offset: .4 },
749
+ { transform: `rotate(${a + 540}deg) translateX(0px) scale(0)`, opacity: 0, offset: .42 }, { transform: `rotate(${a + 540}deg) translateX(0px) scale(0)`, opacity: 0 }], { duration: D }));
750
+ }
751
+ const anel = peca(m, `left:130px;top:70px;width:60px;height:60px;border-radius:50%;border:3px solid ${ACENTO}`);
752
+ A.push(loop(anel, [{ transform: 'scale(0)', opacity: 0 }, { transform: 'scale(0)', opacity: 0, offset: .4 }, { transform: 'scale(.5)', opacity: 1, offset: .41, easing: 'cubic-bezier(.1,.8,.3,1)' },
753
+ { transform: 'scale(4)', opacity: 0, offset: .62 }, { transform: 'scale(4)', opacity: 0 }], { duration: D }));
754
+ const p = palavra(m, 'left:0;right:0;top:76px;text-align:center;font-size:50px;font-weight:900');
755
+ A.push(loop(p, [{ transform: T(0, 0, -90, 0) }, { transform: T(0, 0, -90, 0), offset: .4, easing: OVER }, { transform: T(0, 0, 6, 1.25), offset: .5 },
756
+ { transform: T(0, 0, 0, 1), offset: .58 }, { transform: T(0, 0, 0, 1), offset: .86, easing: 'cubic-bezier(.6,0,1,.5)' }, { transform: T(0, 0, 200, 0) }], { duration: D }));
757
+ }
758
+
759
+ /* Caça-níquel: cada letra roda como num rolo de cassino, borrando, e trava uma de cada vez. */
760
+ function cenaSlot(m, A) {
761
+ const D = 3400, h = 70, mix = 'KZAXQVTEWYUBGH';
762
+ const caixa = peca(m, `left:0;right:0;top:56px;height:${h}px;display:flex;justify-content:center;gap:5px`);
763
+ 'PRIMO'.split('').forEach((ch, i) => {
764
+ const seq = mix.slice(i, i + 7).split('').concat(ch), fim = -(seq.length - 1) * h;
765
+ const jan = el('div', { style: `height:${h}px;width:48px;overflow:hidden;border-radius:12px;background:${VEU}` });
766
+ const fita = el('div', { style: `display:flex;flex-direction:column;align-items:center;font:900 54px/${h}px var(--f-display);color:${TINTA}` }, seq.map((c) => el('span', { text: c })));
767
+ jan.appendChild(fita); caixa.appendChild(jan);
768
+ A.push(loop(fita, [{ transform: 'translateY(0)', filter: 'blur(0px)' }, { transform: 'translateY(0)', filter: 'blur(0px)', offset: .04, easing: 'ease-in' },
769
+ { transform: `translateY(${fim * .6}px)`, filter: 'blur(4px)', offset: .2 + i * .05 }, { transform: `translateY(${fim - 16}px)`, filter: 'blur(0px)', offset: .3 + i * .07, easing: 'ease-out' },
770
+ { transform: `translateY(${fim}px)`, filter: 'blur(0px)', offset: .36 + i * .07 }, { transform: `translateY(${fim}px)`, filter: 'blur(0px)', offset: .92 },
771
+ { transform: 'translateY(0)', filter: 'blur(3px)' }], { duration: D }));
772
+ A.push(loop(jan, [{ boxShadow: '0 0 0 0 transparent' }, { boxShadow: '0 0 0 0 transparent', offset: .35 + i * .07 },
773
+ { boxShadow: `0 0 0 3px ${ACENTO}`, offset: .37 + i * .07 }, { boxShadow: '0 0 0 0 transparent', offset: .5 + i * .07 }, { boxShadow: '0 0 0 0 transparent' }], { duration: D }));
774
+ });
775
+ }
776
+
777
+ /* Túnel: quadrados saem do centro girando sem parar, e a palavra pulsa no meio como batida. */
778
+ function cenaTunel(m, A) {
779
+ for (let i = 0; i < 6; i++) {
780
+ const q = peca(m, `left:110px;top:50px;width:100px;height:100px;border-radius:16px;border:2.5px solid ${i % 2 ? ACENTO : TINTA}`);
781
+ A.push(loop(q, [{ transform: 'scale(0) rotate(0deg)', opacity: 0 }, { transform: 'scale(.7) rotate(40deg)', opacity: 1, offset: .3 }, { transform: 'scale(4.2) rotate(130deg)', opacity: 0 }],
782
+ { duration: 2400, delay: -i * 400, easing: 'cubic-bezier(.5,0,.9,.5)' }));
783
+ }
784
+ const fundo = peca(m, `left:122px;top:80px;width:76px;height:40px;border-radius:10px;background:var(--palco)`);
785
+ const p = palavra(m, 'left:0;right:0;top:82px;text-align:center;font-size:36px;font-weight:900');
786
+ [fundo, p].forEach((e) => A.push(loop(e, [{ transform: 'scale(1)', easing: 'ease-out' }, { transform: 'scale(1.16)', offset: .12, easing: 'ease-in-out' }, { transform: 'scale(1)', offset: .5 }, { transform: 'scale(1)' }], { duration: 800 })));
787
+ }
788
+
789
+ /* Equalizador: dezoito barras dançam em ritmos diferentes e a palavra quica na batida. */
790
+ function cenaEqualizador(m, A) {
791
+ for (let i = 0; i < 18; i++) {
792
+ const b = peca(m, `left:${35 + i * 14}px;top:76px;width:9px;height:104px;border-radius:5px;background:${i % 3 ? TINTA : ACENTO};transform-origin:bottom`);
793
+ const hs = [.2, .9, .4, 1, .3].map((v, k) => ((v + i * .37 + k * .21) % 1) * .85 + .15);
794
+ A.push(loop(b, [...hs, hs[0]].map((v) => ({ transform: `scaleY(${v})` })), { duration: 900 + (i % 4) * 140, easing: 'ease-in-out' }));
795
+ }
796
+ const p = palavra(m, 'left:0;right:0;top:18px;text-align:center;font-size:42px;font-weight:900');
797
+ A.push(loop(p, [{ transform: T(0, 0, 0, 1), easing: 'ease-out' }, { transform: T(0, -8, -3, 1.08), offset: .2, easing: 'ease-in' }, { transform: T(0, 0, 0, 1), offset: .5 },
798
+ { transform: T(0, -8, 3, 1.08), offset: .7, easing: 'ease-in' }, { transform: T(0, 0, 0, 1) }], { duration: 900 }));
799
+ }
800
+
801
+ /* Cartas: um baralho cai na mesa, abre em leque, junta de novo e a palavra aparece embaixo. */
802
+ function cenaCartas(m, A) {
803
+ const D = 3600;
804
+ for (let i = 0; i < 5; i++) {
805
+ const topo = i === 4, ang = (i - 2) * 16;
806
+ const c = peca(m, `left:125px;top:22px;width:70px;height:100px;box-sizing:border-box;border-radius:11px;border:2px solid ${topo ? ACENTO : TINTA};background:${topo ? ACENTO : 'var(--palco)'};transform-origin:50% 140%;display:grid;place-items:center;font:900 30px/1 var(--f-display);color:var(--sobre)`, topo ? '✦' : '');
807
+ A.push(loop(c, [{ transform: T(0, -150, -20, 1), opacity: 0 }, { transform: T(0, -150, -20, 1), opacity: 0, offset: i * .02, easing: OVER },
808
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .12 + i * .02 }, { transform: T(0, 0, 0, 1), opacity: 1, offset: .2, easing: OVER },
809
+ { transform: T((i - 2) * 24, Math.abs(i - 2) * 5, ang, 1), opacity: 1, offset: .36 }, { transform: T((i - 2) * 24, Math.abs(i - 2) * 5, ang, 1), opacity: 1, offset: .6, easing: CHICOTE },
810
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .7 }, { transform: T(0, 0, 0, 1), opacity: 1, offset: .88 + i * .015, easing: 'ease-in' },
811
+ { transform: T(0, -170, 30, 1), opacity: 0, offset: .97 + i * .005 }, { transform: T(0, -170, 30, 1), opacity: 0 }], { duration: D }));
812
+ }
813
+ const p = palavra(m, 'left:0;right:0;top:146px;text-align:center;font-size:30px');
814
+ A.push(loop(p, [{ opacity: 0, transform: 'translateY(14px)' }, { opacity: 0, transform: 'translateY(14px)', offset: .68, easing: MD3 }, { opacity: 1, transform: 'translateY(0)', offset: .78 },
815
+ { opacity: 1, transform: 'translateY(0)', offset: .9 }, { opacity: 0, transform: 'translateY(0)' }], { duration: D }));
816
+ }
817
+
818
+ /* Traço: uma linha azul se desenha pela tela com uma bolinha na ponta, e a palavra se revela atrás dela. */
819
+ function cenaTraco(m, A) {
820
+ const D = 3400, d = 'M18 150 C 70 10, 130 10, 160 100 S 250 190, 302 40';
821
+ const NS = ['http:', '', 'www.w3.org', '2000', 'svg'].join('/'), svg = document.createElementNS(NS, 'svg');
822
+ svg.setAttribute('viewBox', '0 0 320 200');
823
+ svg.style.cssText = 'position:absolute;left:0;top:0;width:320px;height:200px;overflow:visible';
824
+ const linha = (w, c, op) => { const p = document.createElementNS(NS, 'path'); Object.entries({ d, fill: 'none', stroke: c, 'stroke-width': w, 'stroke-linecap': 'round', pathLength: 100, 'stroke-dasharray': 100, 'stroke-dashoffset': 100, opacity: op }).forEach(([k, v]) => p.setAttribute(k, v)); svg.appendChild(p); return p; };
825
+ const fina = linha(2, 'var(--cor)', .25), grossa = linha(7, 'var(--ambar)', 1);
826
+ m.appendChild(svg);
827
+ const desenha = (atraso) => [{ strokeDashoffset: 100 }, { strokeDashoffset: 100, offset: atraso, easing: 'cubic-bezier(.6,0,.3,1)' }, { strokeDashoffset: 0, offset: atraso + .4 },
828
+ { strokeDashoffset: 0, offset: .82, easing: 'cubic-bezier(.6,0,.3,1)' }, { strokeDashoffset: -100, offset: .98 }, { strokeDashoffset: -100 }];
829
+ A.push(loop(grossa, desenha(.04), { duration: D }));
830
+ A.push(loop(fina, desenha(.1), { duration: D }));
831
+ const ponta = peca(m, `left:-8px;top:-8px;width:16px;height:16px;border-radius:50%;background:var(--palco);border:3px solid ${ACENTO};box-sizing:border-box;offset-path:path('${d}');offset-rotate:0deg`);
832
+ A.push(loop(ponta, [{ offsetDistance: '0%', opacity: 0 }, { offsetDistance: '0%', opacity: 1, offset: .04, easing: 'cubic-bezier(.6,0,.3,1)' }, { offsetDistance: '100%', opacity: 1, offset: .44 },
833
+ { offsetDistance: '100%', opacity: 0, offset: .5 }, { offsetDistance: '100%', opacity: 0 }], { duration: D }));
834
+ const p = palavra(m, 'left:0;right:0;top:78px;text-align:center;font-family:var(--f-serifa);font-style:italic;font-weight:400;font-size:58px;letter-spacing:-.01em', 'Primo');
835
+ A.push(loop(p, [{ clipPath: 'inset(0 100% 0 0)' }, { clipPath: 'inset(0 100% 0 0)', offset: .14, easing: 'cubic-bezier(.6,0,.3,1)' }, { clipPath: 'inset(0 0% 0 0)', offset: .46 },
836
+ { clipPath: 'inset(0 0% 0 0)', offset: .84, easing: MD3 }, { clipPath: 'inset(0 0 0 100%)', offset: .96 }, { clipPath: 'inset(0 0 0 100%)' }], { duration: D }));
837
+ }
838
+
839
+ /* Estilhaço: um impacto quebra a palavra, as letras e os cacos voam e tudo se junta de volta no lugar. */
840
+ function cenaEstilhaco(m, A) {
841
+ const D = 3200, dx = [-130, -60, 10, 80, 140], dy = [-80, 70, -95, 85, -60], rr = [-220, 170, -130, 250, -190];
842
+ letras(m, 'left:0;right:0;top:64px;justify-content:center;font-size:66px;font-weight:900').forEach((s, i) => {
843
+ A.push(loop(s, [{ transform: T(), opacity: 1 }, { transform: T(), opacity: 1, offset: .26 }, { transform: T(-3, 2), opacity: 1, offset: .28 },
844
+ { transform: T(dx[i], dy[i], rr[i], .5), opacity: 0, offset: .46, easing: 'cubic-bezier(.1,.8,.3,1)' }, { transform: T(dx[i], dy[i], rr[i], .5), opacity: 0, offset: .56, easing: OVER },
845
+ { transform: T(), opacity: 1, offset: .76 + i * .02 }, { transform: T(), opacity: 1 }].map((k, j) => (j === 3 ? { ...k, easing: undefined } : k)).map((k) => { if (k.easing === undefined) delete k.easing; return k; }), { duration: D }));
846
+ });
847
+ const flash = peca(m, `left:110px;top:50px;width:100px;height:100px;border-radius:50%;background:radial-gradient(closest-side, ${ACENTO}, transparent)`);
848
+ A.push(loop(flash, [{ transform: 'scale(0)', opacity: 0 }, { transform: 'scale(0)', opacity: 0, offset: .27 }, { transform: 'scale(1)', opacity: .9, offset: .29 },
849
+ { transform: 'scale(2.6)', opacity: 0, offset: .42 }, { transform: 'scale(2.6)', opacity: 0 }], { duration: D }));
850
+ for (let i = 0; i < 12; i++) {
851
+ const a = i * 30 + 10, r = 90 + (i % 3) * 30;
852
+ const caco = peca(m, `left:154px;top:94px;width:12px;height:12px;background:${i % 2 ? ACENTO : TINTA};clip-path:polygon(50% 0,100% 100%,0 70%)`);
853
+ A.push(loop(caco, [{ transform: `rotate(${a}deg) translateX(0) rotate(0deg) scale(0)`, opacity: 0 }, { transform: `rotate(${a}deg) translateX(0) rotate(0deg) scale(0)`, opacity: 0, offset: .28 },
854
+ { transform: `rotate(${a}deg) translateX(10px) rotate(0deg) scale(1)`, opacity: 1, offset: .29, easing: 'cubic-bezier(.1,.8,.3,1)' },
855
+ { transform: `rotate(${a}deg) translateX(${r}px) rotate(${i * 90}deg) scale(.5)`, opacity: 0, offset: .55 }, { transform: `rotate(${a}deg) translateX(${r}px) rotate(${i * 90}deg) scale(.5)`, opacity: 0 }], { duration: D }));
856
+ }
857
+ }
858
+
859
+ ESTILOS.push(
860
+ { id: 'cubo', nome: 'Cubo', frase: '', ritmo: 'medio', cameras: ['orbita', 'push'], trocas: ['giro', 'zoom'], tipografias: ['pesada', 'poster'], overshoot: .2 },
861
+ { id: 'glitch', nome: 'Glitch', frase: '', ritmo: 'rapido', cameras: ['chicote', 'pulso'], trocas: ['tremor', 'corte'], tipografias: ['tecnica', 'pesada'], overshoot: .05 },
862
+ { id: 'espiral', nome: 'Espiral', frase: '', ritmo: 'rapido', cameras: ['orbita', 'pulso'], trocas: ['zoom', 'encolhe'], tipografias: ['pesada', 'arredondada'], overshoot: .2 },
863
+ { id: 'slot', nome: 'Caça-níquel', frase: '', ritmo: 'rapido', cameras: ['pulso', 'trilho'], trocas: ['corte', 'persiana'], tipografias: ['pesada', 'poster'], overshoot: .1 },
864
+ { id: 'tunel', nome: 'Túnel', frase: '', ritmo: 'rapido', cameras: ['push', 'pulso'], trocas: ['zoom', 'giro'], tipografias: ['geometrica', 'poster'], overshoot: .1 },
865
+ { id: 'mosaico', nome: 'Mosaico', frase: '', ritmo: 'medio', cameras: ['push', 'deriva'], trocas: ['persiana', 'abre'], tipografias: ['geometrica', 'pesada'], overshoot: .15 },
866
+ { id: 'estilhaco', nome: 'Estilhaço', frase: '', ritmo: 'rapido', cameras: ['chicote', 'pulso'], trocas: ['zoom', 'tremor'], tipografias: ['pesada', 'poster'], overshoot: .22 },
867
+ { id: 'contador', nome: 'Contador', frase: '', ritmo: 'medio', cameras: ['push', 'trilho'], trocas: ['empurra', 'corte'], tipografias: ['tecnica', 'geometrica'], overshoot: .06 },
868
+ { id: 'orbita', nome: 'Órbita', frase: '', ritmo: 'medio', cameras: ['orbita', 'cinema'], trocas: ['giro', 'dissolve'], tipografias: ['geometrica'], overshoot: .08 },
869
+ { id: 'equalizador', nome: 'Equalizador', frase: '', ritmo: 'rapido', cameras: ['pulso', 'chicote'], trocas: ['corte', 'tremor'], tipografias: ['pesada', 'poster'], overshoot: .12 },
870
+ { id: 'cartas', nome: 'Cartas', frase: '', ritmo: 'medio', cameras: ['cinema', 'deriva'], trocas: ['abre', 'giro'], tipografias: ['editorial', 'arredondada'], overshoot: .18 },
871
+ { id: 'gota', nome: 'Gota', frase: '', ritmo: 'lento', cameras: ['deriva', 'cinema'], trocas: ['desfoque', 'dissolve'], tipografias: ['serifa_fina', 'editorial'], overshoot: .12 },
872
+ { id: 'split', nome: 'Corte ao meio', frase: '', ritmo: 'rapido', cameras: ['trilho', 'chicote'], trocas: ['persiana', 'empurra'], tipografias: ['pesada', 'tecnica'], overshoot: .04 },
873
+ { id: 'traco', nome: 'Traço', frase: '', ritmo: 'medio', cameras: ['trilho', 'deriva'], trocas: ['abre', 'cortina'], tipografias: ['manuscrita', 'editorial'], overshoot: .08 },
874
+ );
875
+ /* a ordem do carrossel: os mais fortes primeiro, alternando energia */
876
+ const ORDEM = ['brincalhao', 'cubo', 'glitch', 'cinetico', 'espiral', 'energico', 'slot', 'cinema3d', 'tunel', 'mosaico', 'premium', 'estilhaco',
877
+ 'corporativo', 'contador', 'orbita', 'equalizador', 'cartas', 'gota', 'split', 'traco'];
878
+ const NOVAS_CENAS = { orbita: cenaOrbita, gota: cenaGota, glitch: cenaGlitch, contador: cenaContador, cubo: cenaCubo, split: cenaSplit, mosaico: cenaMosaico,
879
+ espiral: cenaEspiral, slot: cenaSlot, tunel: cenaTunel, equalizador: cenaEqualizador, cartas: cenaCartas, traco: cenaTraco, estilhaco: cenaEstilhaco };
880
+
881
+ /* ── as prévias do catálogo ───────────────────────────────────────────── */
882
+ function quadrosAB(m) {
883
+ const base = 'left:50px;top:35px;width:220px;height:130px;box-sizing:border-box;border-radius:14px;display:grid;place-items:center;font:800 54px/1 var(--f-display);letter-spacing:-.04em;';
884
+ return [peca(m, base + `background:${VEU};color:${TINTA};border:2px solid ${TINTA}`, 'A'), peca(m, base + `background:${TINTA};color:var(--palco)`, 'B')];
885
+ }
886
+ const fixo = (o) => [{ ...o, offset: .3 }, { ...o, offset: .7 }];
887
+ const TRANSICOES_KF = {
888
+ encolhe: [[{ transform: 'scale(1)', offset: .3, easing: 'cubic-bezier(.6,0,.9,.4)' }, { transform: 'scale(0)', offset: .5 }], [{ transform: 'scale(0)', offset: .48, easing: OVER }, { transform: 'scale(1)', offset: .7 }]],
889
+ empurra: [[{ transform: 'translateX(0)', offset: .3, easing: MD3 }, { transform: 'translateX(-300px)', offset: .7 }], [{ transform: 'translateX(300px)', offset: .3, easing: MD3 }, { transform: 'translateX(0)', offset: .7 }]],
890
+ zoom: [[{ transform: 'scale(1)', opacity: 1, offset: .3, easing: 'ease-in' }, { transform: 'scale(3)', opacity: 0, offset: .52 }], [{ transform: 'scale(.3)', opacity: 0, offset: .46, easing: 'ease-out' }, { transform: 'scale(1)', opacity: 1, offset: .7 }]],
891
+ chicote: [[{ transform: 'translateX(0) skewX(0deg)', filter: 'blur(0px)', offset: .3, easing: CHICOTE }, { transform: 'translateX(-340px) skewX(-25deg)', filter: 'blur(6px)', offset: .48 }], [{ transform: 'translateX(340px) skewX(-25deg)', filter: 'blur(6px)', offset: .45, easing: 'cubic-bezier(.1,.8,.2,1)' }, { transform: 'translateX(0) skewX(0deg)', filter: 'blur(0px)', offset: .68 }]],
892
+ desfoque: [[{ filter: 'blur(0px)', opacity: 1, offset: .3, easing: LISO }, { filter: 'blur(16px)', opacity: 0, offset: .58 }], [{ filter: 'blur(16px)', opacity: 0, offset: .42, easing: LISO }, { filter: 'blur(0px)', opacity: 1, offset: .7 }]],
893
+ cortina: [fixo({ transform: 'translateY(0)' }), [{ transform: 'translateY(-230px)', offset: .3, easing: 'cubic-bezier(.3,1.25,.6,1)' }, { transform: 'translateY(0)', offset: .66 }]],
894
+ giro: [[{ transform: 'rotateY(0deg)', offset: .3, easing: 'ease-in' }, { transform: 'rotateY(90deg)', offset: .5 }], [{ transform: 'rotateY(-90deg)', offset: .5, easing: 'ease-out' }, { transform: 'rotateY(0deg)', offset: .7 }]],
895
+ tremor: [[{ transform: 'translate(0,0)', opacity: 1, offset: .3 }, { transform: 'translate(-7px,3px)', opacity: 1, offset: .34 }, { transform: 'translate(7px,-4px)', opacity: 1, offset: .38 }, { transform: 'translate(-6px,-3px)', opacity: 1, offset: .42 }, { transform: 'translate(6px,4px)', opacity: 1, offset: .46 }, { transform: 'translate(0,0)', opacity: 1, offset: .499 }, { transform: 'translate(0,0)', opacity: 0, offset: .5 }], [{ opacity: 0, offset: .499 }, { opacity: 1, offset: .5 }]],
896
+ abre: [fixo({ opacity: 1 }), [{ clipPath: 'inset(0 50% 0 50% round 14px)', offset: .3, easing: MD3 }, { clipPath: 'inset(0 0% 0 0% round 14px)', offset: .68 }]],
897
+ dissolve: [[{ opacity: 1, offset: .3, easing: LISO }, { opacity: 0, offset: .7 }], [{ opacity: 0, offset: .3, easing: LISO }, { opacity: 1, offset: .7 }]],
898
+ corte: [[{ opacity: 1, offset: .499 }, { opacity: 0, offset: .5 }], [{ opacity: 0, offset: .499 }, { opacity: 1, offset: .5 }]],
899
+ };
900
+ const completar = (kfs) => [{ ...kfs[0], offset: 0, easing: 'linear' }, ...kfs, { ...kfs[kfs.length - 1], offset: 1, easing: 'linear' }];
901
+
902
+ function previaTransicao(m, id, A) {
903
+ const [a, b] = quadrosAB(m);
904
+ const o = { duration: 1500, direction: 'alternate' };
905
+ if (id === 'persiana') {
906
+ b.style.background = 'transparent';
907
+ const letra = b.textContent; b.textContent = '';
908
+ for (let i = 0; i < 5; i++) {
909
+ const s = peca(b, `left:${i * 44}px;top:0;width:44.5px;height:130px;background:${TINTA};transform-origin:left;${i === 0 ? 'border-radius:14px 0 0 14px;' : ''}${i === 4 ? 'border-radius:0 14px 14px 0;' : ''}`);
910
+ A.push(loop(s, completar([{ transform: 'scaleX(0)', offset: .3 + i * .05, easing: MD3 }, { transform: 'scaleX(1)', offset: .45 + i * .05 }]), o));
911
+ }
912
+ const l = peca(b, 'left:0;top:0;width:220px;height:130px;display:grid;place-items:center;color:var(--palco)', letra);
913
+ A.push(loop(l, completar([{ opacity: 0, offset: .6 }, { opacity: 1, offset: .7 }]), o));
914
+ return;
915
+ }
916
+ const [ka, kb] = TRANSICOES_KF[id];
917
+ A.push(loop(a, completar(ka), o));
918
+ A.push(loop(b, completar(kb), o));
919
+ }
920
+
921
+ function previaCamera(m, id, A) {
922
+ const cam = peca(m, 'left:0;top:0;width:320px;height:200px;transform-style:preserve-3d');
923
+ const fundo = peca(cam, 'left:-60px;top:-40px;width:440px;height:280px;background-image:radial-gradient(color-mix(in srgb, var(--cor) 24%, transparent) 1.4px, transparent 1.7px);background-size:18px 18px');
924
+ const card = peca(cam, `left:96px;top:40px;width:128px;height:84px;box-sizing:border-box;border-radius:14px;background:${VEU};border:2px solid ${TINTA}`);
925
+ peca(card, `left:14px;top:16px;width:26px;height:26px;box-sizing:border-box;border-radius:50%;border:2.5px solid ${TINTA}`);
926
+ peca(card, `left:50px;top:20px;width:52px;height:6px;border-radius:3px;background:${TINTA}`);
927
+ const p = palavra(cam, 'left:0;right:0;top:138px;text-align:center;font-size:28px');
928
+ if (id === 'orbita') {
929
+ fundo.style.transform = 'translateZ(-80px)'; p.style.transform = 'translateZ(50px)';
930
+ A.push(loop(cam, [{ transform: 'rotateY(-30deg) rotateX(8deg)' }, { transform: 'rotateY(30deg) rotateX(8deg)' }], { duration: 3000, direction: 'alternate', easing: LISO }));
931
+ return;
932
+ }
933
+ const M = {
934
+ deriva: (f) => [[{ transform: `translate(${-14 * f}px,${-6 * f}px)` }, { transform: `translate(${14 * f}px,${6 * f}px)` }], { duration: 3200, direction: 'alternate', easing: LISO }],
935
+ pulso: (f) => [[{ transform: 'scale(1)', easing: 'ease-out' }, { transform: `scale(${1 + .14 * f})`, offset: .08, easing: 'ease-in-out' }, { transform: 'scale(1)', offset: .34 }, { transform: 'scale(1)' }], { duration: 700 }],
936
+ trilho: (f) => [[{ transform: `translateX(${50 * f}px)` }, { transform: `translateX(${-50 * f}px)` }], { duration: 2600, direction: 'alternate', easing: LISO }],
937
+ push: (f) => [[{ transform: 'scale(1)' }, { transform: `scale(${1 + .4 * f})` }], { duration: 2600, direction: 'alternate', easing: LISO }],
938
+ chicote: (f) => [[{ transform: 'translateX(0)', filter: 'blur(0px)' }, { transform: 'translateX(0)', filter: 'blur(0px)', offset: .4, easing: CHICOTE }, { transform: `translateX(${-90 * f}px)`, filter: `blur(${3 * f}px)`, offset: .52 }, { transform: `translateX(${-90 * f}px)`, filter: 'blur(0px)', offset: .58 }, { transform: `translateX(${-90 * f}px)`, filter: 'blur(0px)' }], { duration: 1800, direction: 'alternate' }],
939
+ cinema: (f) => [[{ transform: `translateY(${10 * f}px) scale(${1 + .04 * f}) rotate(-1deg)` }, { transform: `translateY(${-10 * f}px) scale(${1 + .16 * f}) rotate(1deg)` }], { duration: 3800, direction: 'alternate', easing: LISO }],
940
+ };
941
+ [[fundo, .35], [card, .7], [p, 1]].forEach(([e, f]) => { const [kf, o] = M[id](f); A.push(loop(e, kf, o)); });
942
+ }
943
+
944
+ function previaTipografia(m, id, A) {
945
+ const D = 2600, centro = 'left:0;right:0;text-align:center;';
946
+ const sai = (e, extra = {}) => [{ opacity: 1, ...extra, offset: .86, easing: 'ease-in' }, { opacity: 0, ...extra }];
947
+ if (id === 'editorial') {
948
+ const a = palavra(m, 'left:52px;top:76px;font-size:40px;font-weight:600;letter-spacing:-.03em', 'Primo');
949
+ const b = palavra(m, 'left:164px;top:70px;font-size:46px;font-family:var(--f-serifa);font-style:italic;font-weight:400;letter-spacing:-.01em', 'motion');
950
+ [a, b].forEach((e, i) => A.push(loop(e, [
951
+ { opacity: 0, transform: 'translateY(24px)' }, { opacity: 0, transform: 'translateY(24px)', offset: .08 + i * .12, easing: MD3 },
952
+ { opacity: 1, transform: 'translateY(0)', offset: .32 + i * .12 }, ...sai(e, { transform: 'translateY(0)' })], { duration: D })));
953
+ } else if (id === 'poster') {
954
+ const e = palavra(m, centro + 'top:50px;font-family:Impact,Haettenschweiler,"Arial Narrow Bold",var(--f-display);font-weight:400;font-size:90px;letter-spacing:0', 'PRIMO');
955
+ A.push(loop(e, [
956
+ { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(30px)' }, { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(30px)', offset: .08, easing: MD3 },
957
+ { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)', offset: .34 }, { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)', offset: .84, easing: MD3 },
958
+ { clipPath: 'inset(0 0 100% 0)', transform: 'translateY(-30px)', offset: .96 }, { clipPath: 'inset(0 0 100% 0)', transform: 'translateY(-30px)' }], { duration: D }));
959
+ } else if (id === 'tecnica') {
960
+ const ls = letras(m, 'left:0;right:0;top:84px;justify-content:center;font-family:var(--f-mono);font-weight:500;font-size:26px;letter-spacing:0', 'primo.run()');
961
+ ls.forEach((s, i) => A.push(loop(s, [{ opacity: 0 }, { opacity: 0, offset: .06 + i * .04 }, { opacity: 1, offset: .061 + i * .04 }, ...sai(s)], { duration: D })));
962
+ const cur = peca(m, `left:236px;top:82px;width:12px;height:30px;background:${TINTA}`);
963
+ A.push(loop(cur, [{ opacity: 1 }, { opacity: 1, offset: .5 }, { opacity: 0, offset: .51 }, { opacity: 0 }], { duration: 700 }));
964
+ } else if (id === 'manuscrita') {
965
+ const e = palavra(m, centro + 'top:56px;font-family:"Snell Roundhand","Segoe Script","Brush Script MT",cursive;font-weight:400;font-size:70px;letter-spacing:0', 'Primo');
966
+ A.push(loop(e, [
967
+ { clipPath: 'inset(0 100% 0 0)', opacity: 1 }, { clipPath: 'inset(0 100% 0 0)', opacity: 1, offset: .08, easing: 'cubic-bezier(.45,0,.55,1)' },
968
+ { clipPath: 'inset(0 0% 0 0)', opacity: 1, offset: .58 }, ...sai(e, { clipPath: 'inset(0 0% 0 0)' })], { duration: D }));
969
+ } else if (id === 'pesada') {
970
+ const e = palavra(m, centro + 'top:58px;font-size:80px;font-weight:900;letter-spacing:-.05em');
971
+ A.push(loop(e, [
972
+ { transform: 'scale(2.6)', opacity: 0 }, { transform: 'scale(2.6)', opacity: 0, offset: .08, easing: 'cubic-bezier(.6,0,1,.6)' },
973
+ { transform: 'scale(.92)', opacity: 1, offset: .2, easing: 'ease-out' }, { transform: 'scale(1)', opacity: 1, offset: .28 }, ...sai(e, { transform: 'scale(1)' })], { duration: D }));
974
+ A.push(loop(m, [{ transform: T() }, { transform: T(), offset: .2 }, { transform: T(-5, 3) }, { transform: T(5, -3) }, { transform: T(-3, -2) }, { transform: T() }].map((k, i) => (i > 1 && i < 5 ? { ...k, offset: .2 + (i - 1) * .025 } : i === 5 ? { ...k, offset: .3 } : k)).concat([{ transform: T() }]), { duration: D }));
975
+ } else if (id === 'serifa_fina') {
976
+ const e = palavra(m, centro + 'top:60px;font-family:var(--f-serifa);font-weight:300;font-size:64px;letter-spacing:.02em', 'Primo');
977
+ A.push(loop(e, [
978
+ { opacity: 0, letterSpacing: '.45em' }, { opacity: 0, letterSpacing: '.45em', offset: .06, easing: LISO },
979
+ { opacity: 1, letterSpacing: '.02em', offset: .5 }, ...sai(e, { letterSpacing: '.02em' })], { duration: D }));
980
+ } else if (id === 'arredondada') {
981
+ letras(m, 'left:0;right:0;top:66px;justify-content:center;font-family:ui-rounded,"SF Pro Rounded","Arial Rounded MT Bold",var(--f-display);font-weight:700;font-size:62px;letter-spacing:-.02em', 'Primo').forEach((s, i) => A.push(loop(s, [
982
+ { transform: T(0, 60, 0, .3), opacity: 0 }, { transform: T(0, 60, 0, .3), opacity: 0, offset: .06 + i * .05, easing: OVER },
983
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .26 + i * .05 }, ...sai(s, { transform: T() })], { duration: D })));
984
+ } else if (id === 'geometrica') {
985
+ const e = palavra(m, 'left:58px;top:74px;font-family:"Avenir Next",Futura,"Century Gothic",var(--f-display);font-weight:500;font-size:36px;letter-spacing:.18em', 'PRIMO');
986
+ const barra = peca(m, `left:60px;top:124px;width:190px;height:4px;background:${TINTA};transform-origin:left`);
987
+ A.push(loop(e, [
988
+ { opacity: 0, transform: 'translateX(-60px)' }, { opacity: 0, transform: 'translateX(-60px)', offset: .06, easing: MD3 },
989
+ { opacity: 1, transform: 'translateX(0)', offset: .34 }, ...sai(e, { transform: 'translateX(0)' })], { duration: D }));
990
+ A.push(loop(barra, [{ transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .24, easing: MD3 }, { transform: 'scaleX(1)', offset: .48 }, ...sai(barra, { transform: 'scaleX(1)' })], { duration: D }));
991
+ }
992
+ }
993
+
994
+ function montarPrevia(grupo, id, palco) {
995
+ const m = mundo(palco), A = [];
996
+ if (parar) { palavra(m, 'left:0;right:0;top:80px;text-align:center;font-size:36px', id === 'poster' ? 'PRIMO' : 'Primo'); return A; }
997
+ if (grupo === 'trocas') previaTransicao(m, id, A);
998
+ else if (grupo === 'cameras') previaCamera(m, id, A);
999
+ else previaTipografia(m, id, A);
1000
+ return A;
1001
+ }
1002
+
1003
+ const cor = (id) => `var(--c-${id})`;
1004
+
1005
+ /* ── passo 1 ────────────────────────────────────────────────────────────── */
1006
+ function passo1() {
1007
+ const grade = $('#grade');
1008
+ let coluna = null;
1009
+ const conhecidos = new Set(((window.GOSTO && window.GOSTO.ESTILOS) || ESTILOS).map((x) => x.id));
1010
+ ORDEM.filter((id) => conhecidos.has(id)).map((id) => ESTILOS.find((x) => x.id === id)).filter(Boolean).forEach((e, i) => {
1011
+ if (i % 2 === 0) { coluna = el('div', { class: 'coluna' }); grade.appendChild(coluna); }
1012
+ const palco = el('div', { class: 'palco' });
1013
+ const card = el('button', { class: 'card', 'aria-pressed': 'false', 'aria-label': e.nome, id: 'card-' + e.id, style: `--cor:${cor(e.id)}` }, [
1014
+ el('span', { class: 'selo', text: 'escolhido' }), palco]);
1015
+ card.addEventListener('click', () => {
1016
+ const on = card.getAttribute('aria-pressed') !== 'true';
1017
+ card.setAttribute('aria-pressed', String(on));
1018
+ if (on) estado.escolhidos.set(e.id, estado.escolhidos.get(e.id) || { gostei: new Set(), texto: '' }); else estado.escolhidos.delete(e.id);
1019
+ atualizarConta();
1020
+ });
1021
+ coluna.appendChild(card);
1022
+ soNaTela(palco, montarCena(e.id, palco));
1023
+ });
1024
+ ligarSetas();
1025
+ }
1026
+
1027
+ /* só anima o que está à vista: são mais de 30 telinhas no carrossel */
1028
+ const vigia = 'IntersectionObserver' in window ? new IntersectionObserver((es) => es.forEach((x) => {
1029
+ (x.target.__anims || []).forEach((a) => (x.isIntersecting ? a.play() : a.pause()));
1030
+ }), { rootMargin: '0px 120px' }) : null;
1031
+ function soNaTela(palco, anims) { palco.__anims = anims; if (vigia) vigia.observe(palco); }
1032
+
1033
+ /* cada coluna tem uma vaga relativa ao começo da página: 0 a vistas-1 aparecem inteiras, a de cada lado
1034
+ aparece esmaecida e as outras ficam escondidas; avançar só muda o deslocamento, e a coluna que dá a volta
1035
+ pula de um lado pro outro sem animação, enquanto está invisível */
1036
+ function ligarSetas(t = $('#grade'), volta = $('#setaVolta'), vai = $('#setaVai'), caixa = $('#carrossel')) {
1037
+ const cols = [...t.children], N = cols.length, GAP = 20;
1038
+ let ini = 0, vistas = 3, passoX = 0, vagaAntes = new Map();
1039
+ const vaga = (i) => { let v = ((i - ini) % N + N) % N; if (v >= N - 2) v -= N; return v; };
1040
+ function medir() {
1041
+ const W = t.clientWidth;
1042
+ vistas = W < 560 ? 1 : W < 860 ? 2 : 3;
1043
+ const cw = (W - GAP * (vistas - 1)) / vistas;
1044
+ passoX = cw + GAP;
1045
+ cols.forEach((c) => { c.style.width = cw + 'px'; });
1046
+ t.style.height = (cols[0].offsetHeight || cw * 1.25 + GAP) + 'px';
1047
+ pintar(true);
1048
+ }
1049
+ function pintar(semAnim) {
1050
+ cols.forEach((c, i) => {
1051
+ const v = vaga(i), antes = vagaAntes.get(c);
1052
+ const salta = semAnim || (antes != null && Math.abs(v - antes) > vistas + 1);
1053
+ c.classList.toggle('salta', salta);
1054
+ const dentro = v >= 0 && v < vistas, vizinha = v === -1 || v === vistas;
1055
+ const op = dentro ? 1 : vizinha ? .4 : 0, esc = dentro ? 1 : .9;
1056
+ c.style.transform = `translateX(${v * passoX}px) scale(${esc})`;
1057
+ c.style.opacity = op;
1058
+ c.style.filter = dentro ? 'none' : 'blur(2px)';
1059
+ c.classList.toggle('vis', dentro);
1060
+ c.classList.toggle('longe', !dentro);
1061
+ vagaAntes.set(c, v);
1062
+ });
1063
+ if (semAnim) requestAnimationFrame(() => cols.forEach((c) => c.classList.remove('salta')));
1064
+ }
1065
+ const andar = (n) => { ini = ((ini + n * vistas) % N + N) % N; pintar(false); };
1066
+ volta.addEventListener('click', () => andar(-1));
1067
+ vai.addEventListener('click', () => andar(1));
1068
+ t.addEventListener('keydown', (ev) => { if (ev.key === 'ArrowRight') { ev.preventDefault(); andar(1); } if (ev.key === 'ArrowLeft') { ev.preventDefault(); andar(-1); } });
1069
+ /* arrastar com o dedo ou o trackpad também passa a página */
1070
+ let x0 = null;
1071
+ t.addEventListener('pointerdown', (ev) => { x0 = ev.clientX; });
1072
+ let arrastou = false;
1073
+ addEventListener('pointerup', (ev) => { if (x0 == null) return; const dx = ev.clientX - x0; x0 = null; if (Math.abs(dx) > 50) { arrastou = true; andar(dx < 0 ? 1 : -1); } });
1074
+ t.addEventListener('click', (ev) => { if (arrastou) { arrastou = false; ev.stopPropagation(); ev.preventDefault(); } }, true);
1075
+ let rodaPronta = true;
1076
+ caixa.addEventListener('wheel', (ev) => {
1077
+ if (Math.abs(ev.deltaX) < Math.abs(ev.deltaY) || Math.abs(ev.deltaX) < 20) return;
1078
+ ev.preventDefault();
1079
+ if (rodaPronta) { rodaPronta = false; andar(ev.deltaX > 0 ? 1 : -1); setTimeout(() => { rodaPronta = true; }, 700); }
1080
+ }, { passive: false });
1081
+ try { new ResizeObserver(medir).observe(t); } catch { addEventListener('resize', medir); }
1082
+ medir();
1083
+ }
1084
+
1085
+ function mostrar(n) {
1086
+ [1, 2, 3, 4].forEach((i) => { $('#passo' + i).hidden = i !== n; });
1087
+ const t = { 1: ['Qual movimento tem a sua cara?', 'Toque nos estilos que você curtiu. Pode ser mais de um. O PrimoCode aprende com isso e usa nos próximos vídeos.'],
1088
+ 2: ['Quase lá. Mais disso ou menos disso?', ''],
1089
+ 3: ['E o que não pode ter?', 'Isso pesa contra no diretor. Marca só o que te incomoda de verdade.'],
1090
+ 4: ['Pronto. Seu próximo vídeo já sai com a sua cara.', ''] }[n];
1091
+ $('#titulo').textContent = t[0];
1092
+ window.scrollTo({ top: 0, behavior: parar ? 'auto' : 'smooth' });
1093
+ }
1094
+
1095
+ function chip(texto, ligado, aoTrocar) {
1096
+ const c = el('button', { class: 'chip', 'aria-pressed': String(ligado), text: texto });
1097
+ c.addEventListener('click', () => { const on = c.getAttribute('aria-pressed') !== 'true'; c.setAttribute('aria-pressed', String(on)); aoTrocar(on); });
1098
+ return c;
1099
+ }
1100
+
1101
+ /* ── passo 2 ────────────────────────────────────────────────────────────── */
1102
+ function svgSeta(d) {
1103
+ const NS = ['http:', '', 'www.w3.org', '2000', 'svg'].join('/'), s = document.createElementNS(NS, 'svg'), p = document.createElementNS(NS, 'path');
1104
+ s.setAttribute('viewBox', '0 0 24 24'); s.setAttribute('aria-hidden', 'true'); p.setAttribute('d', d); s.appendChild(p); return s;
1105
+ }
1106
+ const RISCOS = { rapido: 3, lento: 2 };
1107
+ function opcao(k, t, ligado, aoTrocar) {
1108
+ const ic = el('span', { class: 'ic ic-' + k, 'aria-hidden': 'true' }, Array.from({ length: RISCOS[k] || 1 }, () => el('i')));
1109
+ const b = el('button', { class: 'op', 'aria-pressed': String(ligado) }, [ic, el('span', { text: t })]);
1110
+ b.addEventListener('click', () => { const on = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', String(on)); aoTrocar(on); });
1111
+ return b;
1112
+ }
1113
+ /* uma pergunta só, valendo pra todos os escolhidos: escolher 15 não vira 15 perguntas */
1114
+ const geral = { gostei: new Set(), texto: '' };
1115
+ function passo2() {
1116
+ const lista = $('#lista2'); lista.replaceChildren();
1117
+ const ids = [...estado.escolhidos.keys()];
1118
+ let fila;
1119
+ if (ids.length <= 3) {
1120
+ fila = el('div', { class: 'mini-fila' });
1121
+ ids.forEach((id) => { const palco = el('div', { class: 'palco' }); fila.appendChild(palco); soNaTela(palco, montarCena(id, palco)); });
1122
+ } else {
1123
+ /* o carrossel infinito precisa de um vizinho de cada lado: com 4 ou 5, repete a fila */
1124
+ const voltas = ids.length < 6 ? ids.concat(ids) : ids;
1125
+ const trilho = el('div', { class: 'trilho', tabindex: '0', 'aria-label': 'Os motions que você escolheu' },
1126
+ voltas.map((id) => { const palco = el('div', { class: 'palco' }); soNaTela(palco, montarCena(id, palco)); return el('div', { class: 'coluna' }, [palco]); }));
1127
+ const seta = (lado, rot) => el('button', { class: 'seta seta-' + lado, 'aria-label': rot }, [svgSeta(lado === 'vai' ? 'M9 5l7 7-7 7' : 'M15 5l-7 7 7 7')]);
1128
+ const volta = seta('volta', 'Voltar'), vai = seta('vai', 'Avançar');
1129
+ fila = el('div', { class: 'carrossel' }, [volta, trilho, vai]);
1130
+ requestAnimationFrame(() => ligarSetas(trilho, volta, vai, fila));
1131
+ }
1132
+ const ops = (lista, tem, poe) => el('div', { class: 'ops' }, lista.map(([k, t]) => opcao(k, t, tem(k), poe(k))));
1133
+ const mais = ops(GOSTEI, (k) => geral.gostei.has(k), (k) => (on) => on ? geral.gostei.add(k) : geral.gostei.delete(k));
1134
+ const menos = ops(NAO_QUERO, (k) => estado.nao.has(k), (k) => (on) => on ? estado.nao.add(k) : estado.nao.delete(k));
1135
+ lista.append(fila,
1136
+ el('div', { class: 'item item-ops' }, [el('div', { class: 'grupo mais' }, [el('h3', { text: 'Mais disso' }), mais]), el('div', { class: 'grupo menos' }, [el('h3', { text: 'Menos disso' }), menos])]));
1137
+ }
1138
+
1139
+ /* ── passo 3 ────────────────────────────────────────────────────────────── */
1140
+ function passo3() {
1141
+ const box = $('#chipsNao'); if (box.childElementCount) return;
1142
+ NAO_QUERO.forEach(([k, t]) => box.appendChild(chip(t, estado.nao.has(k), (on) => on ? estado.nao.add(k) : estado.nao.delete(k))));
1143
+ const ta = $('#naoTexto');
1144
+ ta.addEventListener('input', (ev) => { estado.naoTexto = ev.target.value.slice(0, 280); });
1145
+ $('#sugNao').replaceChildren(...sugestoes(SUG_NAO, ta, (v) => { estado.naoTexto = v; }).children);
1146
+ montarCena('nao', $('#palcoNao'));
1147
+ }
1148
+
1149
+ /* ── o gosto vira pesos (o mesmo cálculo que o diretor vai usar) ─────────── */
1150
+ function gostoJson(pulou = false) {
1151
+ return { versao: 1, quando: new Date().toISOString(), pulou,
1152
+ escolhidos: [...estado.escolhidos].map(([id]) => ({ id, gostei: [...geral.gostei], texto: '' })), gostei: [...geral.gostei], texto: geral.texto.trim(),
1153
+ nao_quero: [...estado.nao], nao_texto: estado.naoTexto.trim(), catalogo: [...estado.extras] };
1154
+ }
1155
+ function pesos(g) {
1156
+ const p = { ritmo: { rapido: 1, medio: 1, lento: 1 }, cameras: {}, trocas: {}, tipografias: {}, overshoot: null };
1157
+ const mult = (o, k, m) => { o[k] = Math.min(5, (o[k] || 1) * m); };
1158
+ const chips = new Set(g.escolhidos.flatMap((x) => x.gostei));
1159
+ g.escolhidos.forEach(({ id }) => {
1160
+ const e = ESTILOS.find((x) => x.id === id);
1161
+ mult(p.ritmo, e.ritmo, 1.8);
1162
+ e.cameras.forEach((c) => mult(p.cameras, c, 2.2)); e.trocas.forEach((t) => mult(p.trocas, t, 2.2)); e.tipografias.forEach((t) => mult(p.tipografias, t, 2.2));
1163
+ });
1164
+ (g.catalogo || []).forEach((k) => { const [gr, id] = k.split(':'); if (p[gr]) mult(p[gr], id, 2.2); });
1165
+ if (chips.has('rapido')) mult(p.ritmo, 'rapido', 1.5);
1166
+ if (chips.has('calmo')) { mult(p.ritmo, 'lento', 1.5); mult(p.trocas, 'tremor', .4); mult(p.trocas, 'chicote', .4); }
1167
+ if (chips.has('quique')) { mult(p.trocas, 'giro', 1.4); mult(p.trocas, 'encolhe', 1.4); }
1168
+ if (chips.has('tres_d')) { mult(p.cameras, 'orbita', 1.5); mult(p.cameras, 'cinema', 1.5); mult(p.trocas, 'giro', 1.4); }
1169
+ if (chips.has('camera')) ['pulso', 'chicote', 'orbita'].forEach((c) => mult(p.cameras, c, 1.3));
1170
+ if (chips.has('cortes')) { mult(p.trocas, 'corte', 1.4); mult(p.trocas, 'chicote', 1.4); }
1171
+ if (g.nao_quero.includes('lento')) mult(p.ritmo, 'lento', .3);
1172
+ if (g.nao_quero.includes('liso')) { mult(p.trocas, 'dissolve', .4); mult(p.trocas, 'desfoque', .4); }
1173
+ if (g.nao_quero.includes('exagero')) { mult(p.trocas, 'tremor', .4); mult(p.trocas, 'chicote', .4); }
1174
+ if (g.nao_quero.includes('tremido')) mult(p.trocas, 'tremor', .2);
1175
+ if (g.escolhidos.length) {
1176
+ let o = g.escolhidos.reduce((s, { id }) => s + ESTILOS.find((x) => x.id === id).overshoot, 0) / g.escolhidos.length;
1177
+ if (chips.has('quique')) o += .05; if (g.nao_quero.includes('exagero')) o -= .05;
1178
+ p.overshoot = Math.max(0, Math.min(.3, o));
1179
+ }
1180
+ return p;
1181
+ }
1182
+ const topo = (o, n) => Object.entries(o).filter(([, v]) => v > 1).sort((a, b) => b[1] - a[1]).slice(0, n).map(([k]) => k.replace('_', ' '));
1183
+ function resumo(g) {
1184
+ const nomeG = Object.fromEntries(GOSTEI), nomeN = Object.fromEntries(NAO_QUERO);
1185
+ const partes = g.escolhidos.map(({ id }) => ESTILOS.find((x) => x.id === id).nome);
1186
+ const doCat = (g.catalogo || []).map((k) => { const [gr, id] = k.split(':'); const c = CATALOGO.find((x) => x.grupo === gr); const it = c && c.itens.find((x) => x[0] === id); return it ? it[1] : id; });
1187
+ if (doCat.length) partes.push(`${doCat.join(', ')} do catálogo`);
1188
+ let s = `Você curtiu ${partes.join(' e ')}.`;
1189
+ if (g.gostei && g.gostei.length) s += ` O que pegou: ${g.gostei.map((k) => nomeG[k]).join(', ')}.`;
1190
+ if (g.nao_quero.length) s += ` Não quer ${g.nao_quero.map((k) => nomeN[k]).join(', ')}.`;
1191
+ const txt = [g.texto, g.nao_texto].filter(Boolean);
1192
+ if (txt.length) s += ` Nas suas palavras: “${txt.join(' · ')}”.`;
1193
+ return s;
1194
+ }
1195
+
1196
+ /* ── passo 4 ────────────────────────────────────────────────────────────── */
1197
+
1198
+
1199
+ $('#ir2').addEventListener('click', () => { passo2(); mostrar(2); });
1200
+ $('#volta1').addEventListener('click', () => mostrar(1));
1201
+ let salvando = false;
1202
+ $('#ir3').addEventListener('click', () => {
1203
+ if (salvando) return;
1204
+ salvando = true; erro(''); $('#ir3').disabled = true;
1205
+ mandar('/salvar', gostoJson()).then(() => mostrar(4)).catch((e) => {
1206
+ salvando = false; $('#ir3').disabled = false; $('#ir3').textContent = 'Tentar de novo';
1207
+ erro('Não consegui salvar: ' + e.message + '. O PrimoCode ainda está aberto no terminal?');
1208
+ });
1209
+ });
1210
+ $('#pular').addEventListener('click', () => {
1211
+ mandar('/pular', {}).then(() => {
1212
+ mostrar(4);
1213
+ $('#titulo').textContent = 'Tudo bem, pulei.';
1214
+ $('#fimSub').textContent = 'O PrimoCode segue sem gosto salvo. Pra escolher depois: /gosto refazer.';
1215
+ }).catch((e) => erro('Não consegui pular: ' + e.message));
1216
+ });
1217
+ $('#volta2').addEventListener('click', () => mostrar(estado.escolhidos.size ? 2 : 1));
1218
+ $('#salvar').addEventListener('click', () => { passo4(); mostrar(4); });
1219
+ $('#finalizar').addEventListener('click', (ev) => {
1220
+ const b = ev.currentTarget;
1221
+ try { window.close(); } catch { /* aba que o navegador não deixa fechar */ }
1222
+ setTimeout(() => { $('#fimSub').textContent = 'Pode fechar esta aba. O PrimoCode já está com o seu gosto.'; b.hidden = true; }, 250);
1223
+ });
1224
+ /* ── catálogo completo: todos os tipos de motion do diretor, animados e escolhíveis ── */
1225
+ const CATALOGO = [
1226
+ { grupo: 'trocas', titulo: 'Transições', sub: 'como uma cena vira a outra', itens: [['encolhe', 'encolhe'], ['empurra', 'empurra'], ['zoom', 'zoom'], ['persiana', 'persiana'], ['chicote', 'chicote'], ['desfoque', 'desfoque'], ['cortina', 'cortina'], ['giro', 'giro'], ['tremor', 'tremor'], ['abre', 'abre'], ['dissolve', 'dissolve'], ['corte', 'corte seco']] },
1227
+ { grupo: 'cameras', titulo: 'Câmeras', sub: 'como a câmera se mexe', itens: [['deriva', 'deriva'], ['pulso', 'pulso'], ['trilho', 'trilho'], ['orbita', 'órbita'], ['push', 'push'], ['chicote', 'chicote'], ['cinema', 'cinema']] },
1228
+ { grupo: 'tipografias', titulo: 'Tipografias', sub: 'como a letra entra', itens: [['editorial', 'editorial'], ['poster', 'pôster'], ['tecnica', 'técnica'], ['manuscrita', 'manuscrita'], ['pesada', 'pesada'], ['serifa_fina', 'serifa fina'], ['arredondada', 'arredondada'], ['geometrica', 'geométrica']] },
1229
+ ];
1230
+ const TIPO = { trocas: 'Transição', cameras: 'Câmera', tipografias: 'Tipografia' };
1231
+ function montarCatalogo() {
1232
+ const grade = $('#grade');
1233
+ CATALOGO.forEach(({ grupo, sub, itens }) => {
1234
+ itens.forEach(([id, nome]) => {
1235
+ const chave = grupo + ':' + id, palco = el('div', { class: 'palco' });
1236
+ const t = el('button', { class: 'card tile', 'aria-pressed': String(estado.extras.has(chave)), style: '--cor:var(--giz)' },
1237
+ [el('span', { class: 'selo', text: 'escolhido' }), palco, el('div', { class: 'rotulo' }, [el('span', { class: 'tipo', text: TIPO[grupo] }), el('b', { text: nome }), el('small', { text: sub })])]);
1238
+ t.addEventListener('click', () => {
1239
+ const on = !estado.extras.has(chave);
1240
+ if (on) estado.extras.add(chave); else estado.extras.delete(chave);
1241
+ t.setAttribute('aria-pressed', String(on));
1242
+ atualizarConta();
1243
+ });
1244
+ grade.appendChild(t);
1245
+ soNaTela(palco, montarPrevia(grupo, id, palco));
1246
+ });
1247
+ });
1248
+ }
1249
+ function atualizarConta() {
1250
+ const n = estado.escolhidos.size, x = estado.extras.size, partes = [];
1251
+ if (n) partes.push(`${n} estilo${n > 1 ? 's' : ''}`);
1252
+ if (x) partes.push(`${x} do catálogo`);
1253
+ $('#ir2').disabled = n + x === 0;
1254
+ }
1255
+
1256
+ /* No produto, a tela é aberta pelo próprio PrimoCode (servidor local com token) e o
1257
+ gosto vai num POST /salvar, que grava ~/.primocode/gosto-motion.json sozinho.
1258
+ Neste protótipo não há servidor, então só guarda na memória da página. */
1259
+ function mandar(rota, corpo) {
1260
+ return fetch(rota, { method: 'POST', headers: { 'content-type': 'application/json', 'x-gosto-token': TOKEN }, body: JSON.stringify(corpo) })
1261
+ .then((r) => r.json().then((j) => { if (!r.ok || !j.ok) throw new Error(j.error || ('erro ' + r.status)); return j; }));
1262
+ }
1263
+ function erro(msg) { const e = $('#erro'); e.hidden = !msg; e.textContent = msg || ''; }
1264
+
1265
+ passo1();
1266
+
1267
+ /* ── o fundo: ondas 3D em degradê, desenhadas na GPU ───────────────────────
1268
+ Seis camadas de onda, de trás pra frente; cada uma tem luz pela inclinação
1269
+ (difusa + brilho na crista), então a superfície parece ter volume. */
1270
+ (function ondas() {
1271
+ const cv = $('#ondas');
1272
+ const gl = cv.getContext('webgl', { antialias: false, premultipliedAlpha: false });
1273
+ if (!gl) return; // sem WebGL: fica o degradê do CSS
1274
+ const vs = 'attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}';
1275
+ const fs = `precision mediump float;
1276
+ uniform vec2 r; uniform float t; uniform vec3 ceu, clara, media, funda; uniform vec3 mouse;
1277
+ float ruido(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
1278
+ void main(){
1279
+ vec2 uv = gl_FragCoord.xy / r;
1280
+ float asp = r.x / r.y;
1281
+ float x = (uv.x - 0.5) * asp;
1282
+ float lado = clamp(abs(x) / (0.5 * asp), 0.0, 1.0);
1283
+ /* a onda se mexe sozinha o tempo todo: quatro senos em velocidades diferentes e a crista inteira respirando;
1284
+ o mouse ainda puxa a crista e abre um anel por onde passa */
1285
+ float crista = 0.15 + 0.26 * smoothstep(0.0, 1.0, lado) + 0.035 * sin(t * 0.8)
1286
+ + 0.085 * sin(x * 1.7 + t * 1.15) + 0.05 * sin(x * 3.3 - t * 1.6 + 1.3)
1287
+ + 0.028 * sin(x * 6.1 + t * 2.4) + 0.012 * sin(x * 11.0 - t * 3.1);
1288
+ float perto = exp(-pow((x - mouse.x) / 0.32, 2.0)) * mouse.z;
1289
+ crista += perto * (0.07 + 0.22 * clamp(mouse.y - crista, 0.0, 0.6));
1290
+ float dm = length(vec2(x - mouse.x, uv.y - mouse.y));
1291
+ crista += 0.014 * sin(dm * 26.0 - t * 5.0) * exp(-dm * 3.5) * mouse.z;
1292
+ float d = uv.y - crista;
1293
+ vec3 col = ceu;
1294
+ col = mix(col, media, exp(-pow(d / 0.17, 2.0)) * 0.95);
1295
+ float abaixo = smoothstep(0.03, -0.34, d);
1296
+ col = mix(col, funda, abaixo * (0.65 + 0.3 * lado));
1297
+ /* a fita de luz: uma segunda onda fina, mais rápida, que corre por cima da primeira */
1298
+ float c2 = crista + 0.075 + 0.04 * sin(x * 2.6 - t * 1.9 + 2.0);
1299
+ col = mix(col, clara, exp(-pow((uv.y - c2) / 0.012, 2.0)) * 0.75);
1300
+ float c3 = crista - 0.06 + 0.035 * sin(x * 3.4 + t * 2.2 + 0.7);
1301
+ col = mix(col, clara, exp(-pow((uv.y - c3) / 0.01, 2.0)) * 0.45 * (0.6 + 0.4 * sin(x * 5.0 - t * 2.0)));
1302
+ /* brilho correndo na crista */
1303
+ float brilho = exp(-pow((d + 0.015) / 0.035, 2.0)) * (0.55 + 0.45 * sin(x * 7.0 - t * 3.2));
1304
+ col = mix(col, clara, brilho * 0.6);
1305
+ col = mix(col, clara, exp(-dm * dm / 0.02) * 0.4 * mouse.z * smoothstep(0.2, -0.1, d));
1306
+ col = mix(col, ceu, smoothstep(0.24, 0.62, d));
1307
+ col += (ruido(gl_FragCoord.xy + fract(t * 0.5)) - 0.5) * 0.03;
1308
+ gl_FragColor = vec4(col, 1.0);
1309
+ }`;
1310
+ const sh = (tipo, src) => { const s = gl.createShader(tipo); gl.shaderSource(s, src); gl.compileShader(s); return gl.getShaderParameter(s, gl.COMPILE_STATUS) ? s : null; };
1311
+ const v = sh(gl.VERTEX_SHADER, vs), f = sh(gl.FRAGMENT_SHADER, fs);
1312
+ if (!v || !f) return;
1313
+ const pr = gl.createProgram(); gl.attachShader(pr, v); gl.attachShader(pr, f); gl.linkProgram(pr);
1314
+ if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) return;
1315
+ gl.useProgram(pr);
1316
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
1317
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
1318
+ const lp = gl.getAttribLocation(pr, 'p'); gl.enableVertexAttribArray(lp); gl.vertexAttribPointer(lp, 2, gl.FLOAT, false, 0, 0);
1319
+ const U = (n) => gl.getUniformLocation(pr, n);
1320
+ const uR = U('r'), uT = U('t'), uM = U('mouse');
1321
+ const alvo = [0, .5, 0], m = [0, .5, 0]; // x na largura da onda, y de baixo pra cima, força (some quando o mouse para)
1322
+ let parado = 0;
1323
+ addEventListener('pointermove', (ev) => { alvo[0] = (ev.clientX / innerWidth - .5) * (innerWidth / innerHeight); alvo[1] = 1 - ev.clientY / innerHeight; alvo[2] = 1; parado = performance.now(); }, { passive: true });
1324
+ const rgb = (h) => { h = h.trim().replace('#', ''); if (h.length === 3) h = h.replace(/./g, '$&$&'); const n = parseInt(h, 16); return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255]; };
1325
+ function cores() {
1326
+ const cs = getComputedStyle(document.documentElement);
1327
+ [['ceu', '--onda-ceu'], ['clara', '--onda-clara'], ['media', '--onda-media'], ['funda', '--onda-funda']].forEach(([u, v]) => gl.uniform3fv(U(u), rgb(cs.getPropertyValue(v) || '#ffffff')));
1328
+ }
1329
+ function medir() {
1330
+ const d = Math.min(window.devicePixelRatio || 1, 1.5) * 0.75;
1331
+ cv.width = Math.max(1, Math.round(innerWidth * d)); cv.height = Math.max(1, Math.round(innerHeight * d));
1332
+ gl.viewport(0, 0, cv.width, cv.height); gl.uniform2f(uR, cv.width, cv.height);
1333
+ }
1334
+ const quadro = (ms) => {
1335
+ if (performance.now() - parado > 1800) alvo[2] = 0;
1336
+ for (let i = 0; i < 3; i++) m[i] += (alvo[i] - m[i]) * (i === 2 ? .04 : .08);
1337
+ gl.uniform3f(uM, m[0], m[1], m[2]); gl.uniform1f(uT, ms / 1000); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
1338
+ };
1339
+ cores(); medir();
1340
+ addEventListener('resize', () => { medir(); if (parar) quadro(4000); });
1341
+ matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { cores(); if (parar) quadro(4000); });
1342
+ new MutationObserver(() => { cores(); if (parar) quadro(4000); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
1343
+ if (parar) { quadro(4000); return; } // reduzir movimento: as ondas ficam paradas
1344
+ const tick = (ms) => { if (!document.hidden) quadro(ms); requestAnimationFrame(tick); };
1345
+ requestAnimationFrame(tick);
1346
+ })();
1347
+ </script>
1348
+
1349
+ </body></html>