Skip to content

Commit 18e6281

Browse files
committed
feat: New entry animation added. New color pallete.
1 parent f4b6885 commit 18e6281

8 files changed

Lines changed: 878 additions & 54 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<p>
1515
<strong>Se você é um recrutador:</strong> O código fala mais que mil palavras. Olhe a pasta <code>engine/</code>.<br />
1616
<strong>Se você é um curioso:</strong> Volte em breve. Ou dê um <code>python main.py</code> e descubra.
17-
18-
17+
</p>
18+
E não esqueça do <code>pip install -r requirements.txt</code>, o <code><code>npm install</code> e o <code>npm run watch:css</code>
1919
<br clear="all" />
20-
</div>
20+
</div>

frontend/assets/css/entrance.css

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/* =========================================================
2+
Kernel entrance animation — styles for the chat empty-state.
3+
Scoped under #empty-state so it never leaks into the chat.
4+
========================================================= */
5+
6+
/* The low-poly globe is a full-screen backdrop behind the whole app. */
7+
#globe {
8+
position: fixed;
9+
inset: 0;
10+
width: 100%;
11+
height: 100%;
12+
display: block;
13+
z-index: 0;
14+
pointer-events: none;
15+
}
16+
17+
/* Keep all the real UI above the globe backdrop. */
18+
.app {
19+
position: relative;
20+
z-index: 1;
21+
}
22+
23+
/* During the entrance the empty-state is a full-screen overlay so the title
24+
can sit to the RIGHT of the full-viewport globe (desktop) / BELOW it
25+
(mobile), matching the original layout. It's click-through except the pill;
26+
the chat hides it (display:none) once a conversation starts. */
27+
#empty-state {
28+
position: fixed;
29+
inset: 0;
30+
z-index: 2;
31+
display: flex;
32+
flex-direction: row;
33+
align-items: center;
34+
justify-content: flex-end; /* title to the right */
35+
gap: 0;
36+
padding: 0 18vw;
37+
pointer-events: none;
38+
}
39+
40+
/* Chrome that stays blank until the entrance reveals it (badge + input).
41+
Set in CSS so there's no flash before entrance.js runs; entrance.js
42+
animates it in (or reveals it instantly if GSAP is unavailable). */
43+
.entrance-init-hidden {
44+
opacity: 0;
45+
visibility: hidden;
46+
}
47+
48+
/* Title stack (revealed by GSAP). Left-aligned on the right side (desktop). */
49+
.entrance-content {
50+
position: relative;
51+
z-index: 1;
52+
display: flex;
53+
flex-direction: column;
54+
align-items: flex-start;
55+
text-align: left;
56+
max-width: 460px;
57+
opacity: 0;
58+
transform: translateY(18px);
59+
}
60+
61+
.entrance-eyebrow {
62+
font-size: 0.72rem;
63+
letter-spacing: 0.42em;
64+
font-weight: 600;
65+
color: #ffffff;
66+
opacity: 0.85;
67+
margin-bottom: 14px;
68+
}
69+
70+
.entrance-headline {
71+
font-size: clamp(1.9rem, 4.4vw, 3rem);
72+
line-height: 1.12;
73+
font-weight: 700;
74+
letter-spacing: -0.01em;
75+
background: linear-gradient(120deg, #ffffff 0%, #d9dadf 45%, #a9aab0 100%);
76+
-webkit-background-clip: text;
77+
background-clip: text;
78+
color: transparent;
79+
padding-bottom: 5px;
80+
}
81+
82+
/* "Kernel" inside the headline — matches the mono entrance choice. */
83+
.entrance-kernel {
84+
-webkit-background-clip: text;
85+
background-clip: text;
86+
-webkit-text-fill-color: #b91c1c;
87+
color: #b91c1c;
88+
}
89+
90+
/* Blinking caret following the typed title. */
91+
.entrance-caret {
92+
display: inline-block;
93+
width: 3px;
94+
height: 1em;
95+
margin-left: 4px;
96+
vertical-align: -0.12em;
97+
background: #e6e7ea;
98+
border-radius: 2px;
99+
box-shadow: 0 0 12px #e6e7ea;
100+
animation: entrance-blink 1s steps(1) infinite;
101+
}
102+
103+
/* -------- Cycling command suggestions (red pill, white text) -------- */
104+
.entrance-suggestion-wrap {
105+
margin-top: 22px;
106+
min-height: 2.4em; /* reserve space so the layout never jumps */
107+
}
108+
109+
.entrance-suggestion {
110+
display: inline-flex;
111+
align-items: center;
112+
max-width: 100%;
113+
padding: 8px 14px;
114+
border-radius: 12px;
115+
background: #b91c1c;
116+
color: #ffffff;
117+
font-family: var(--font-mono, 'JetBrains Mono', monospace);
118+
font-size: clamp(0.82rem, 1.6vw, 1rem);
119+
font-weight: 500;
120+
line-height: 1.3;
121+
letter-spacing: 0.01em;
122+
box-shadow: 0 8px 22px rgba(185, 28, 28, 0.35);
123+
cursor: pointer; /* click to drop the suggestion into the input */
124+
pointer-events: auto; /* re-enable clicks (overlay is click-through) */
125+
}
126+
127+
.entrance-suggestion-text {
128+
white-space: nowrap;
129+
}
130+
131+
.entrance-suggestion-caret {
132+
display: inline-block;
133+
width: 2px;
134+
height: 1.05em;
135+
margin-left: 3px;
136+
vertical-align: -0.12em;
137+
background: #ffffff;
138+
border-radius: 1px;
139+
animation: entrance-blink 1s steps(1) infinite;
140+
}
141+
142+
@keyframes entrance-blink {
143+
0%, 49% { opacity: 1; }
144+
50%, 100% { opacity: 0; }
145+
}
146+
147+
/* Mobile: globe sits up top, title stacks centered below it. */
148+
@media (max-width: 820px) {
149+
#empty-state {
150+
align-items: flex-end;
151+
justify-content: center;
152+
padding: 0 7vw 24vh; /* lift the title above the input */
153+
}
154+
155+
.entrance-content {
156+
align-items: center;
157+
text-align: center;
158+
max-width: 560px;
159+
}
160+
}
161+
162+
/* -------- "Thinking" indicator: a small globe spinner + label -------- */
163+
.thinking-indicator {
164+
display: inline-flex;
165+
align-items: center;
166+
gap: 8px;
167+
}
168+
169+
.thinking-globe {
170+
width: 30px;
171+
height: 30px;
172+
display: block;
173+
flex: 0 0 auto;
174+
}
175+
176+
@media (prefers-reduced-motion: reduce) {
177+
.entrance-caret,
178+
.entrance-suggestion-caret {
179+
animation: none;
180+
}
181+
}

frontend/assets/css/theme.css

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,56 @@ body {
5555
/* ===== Header ===== */
5656
.header {
5757
display: flex;
58-
justify-content: space-between;
58+
justify-content: flex-end;
5959
align-items: center;
6060
gap: 12px;
6161
padding: 8px 24px 0;
6262
background: transparent;
6363
}
6464

65+
/* Gray panel holding the silo pill + pinned-context badge in a column. */
66+
.context-stack {
67+
display: flex;
68+
flex-direction: column;
69+
gap: 4px;
70+
margin-bottom: 6px;
71+
max-width: 100%;
72+
/* width: fit-content; */
73+
}
74+
75+
.context-stack[hidden] {
76+
display: none;
77+
}
78+
79+
/* The panel owns the spacing now — drop the pill's own bottom margin. */
80+
.context-stack .silo-pill {
81+
margin-bottom: 0;
82+
}
83+
84+
/* Pinned-context badge — same look as the silo: gray bg, white icon + text. */
6585
.context-pin-badge {
66-
flex: 1;
67-
min-width: 0;
86+
display: inline-flex;
87+
align-items: center;
88+
gap: 6px;
89+
max-width: 100%;
6890
font-size: 11px;
6991
font-weight: 500;
70-
color: var(--accent);
92+
color: inherit;
93+
overflow: hidden;
94+
text-overflow: ellipsis;
95+
white-space: nowrap;
96+
}
97+
98+
/* The `hidden` attribute must still win over inline-flex. */
99+
.context-pin-badge[hidden] {
100+
display: none;
101+
}
102+
103+
.context-pin-icon {
104+
flex: 0 0 auto;
105+
}
106+
107+
.context-pin-label {
71108
overflow: hidden;
72109
text-overflow: ellipsis;
73110
white-space: nowrap;
@@ -80,7 +117,7 @@ body {
80117
border: 1px solid var(--border);
81118
color: var(--text-muted);
82119
text-transform: uppercase;
83-
border-radius: var(--radius);
120+
border-radius: var(--radius-2xl);
84121
}
85122

86123
.header-badge.online {
@@ -140,7 +177,7 @@ body {
140177
}
141178

142179
.message.user {
143-
background: var(--user-bg);
180+
background: var(--color-red-900);
144181
color: #fff;
145182
font-size: 14.5px;
146183
}
@@ -215,7 +252,7 @@ body {
215252
font-size: 13px;
216253
background: var(--surface-2);
217254
padding: 2px 5px;
218-
color: #93C5FD;
255+
color: var(--color-red-400);
219256
border-radius: var(--radius);
220257
}
221258

@@ -263,9 +300,9 @@ body {
263300
}
264301

265302
.silo-pill {
266-
font-size: 11px;
267-
font-weight: 600;
268-
color: var(--accent);
303+
font-size: 12px;
304+
font-weight: 400;
305+
/* color: var(--accent); */
269306
margin-bottom: 6px;
270307
letter-spacing: 0.02em;
271308
}
@@ -276,6 +313,7 @@ body {
276313
color: var(--text-muted);
277314
padding: 4px 4px 2px;
278315
max-width: 82%;
316+
height: 30px;
279317
transition:
280318
opacity 0.28s ease,
281319
visibility 0.28s ease,
@@ -514,7 +552,7 @@ body {
514552
}
515553

516554
.scope-option-cmd {
517-
color: var(--accent);
555+
color: var(--color-red-500);
518556
font-family: var(--font-mono);
519557
}
520558

frontend/src/components/MessageRow.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,16 @@ export function appendMessageRow(chatBox, opts) {
5454
const meta = document.createElement("div");
5555
meta.className = "message-meta";
5656
const now = new Date();
57-
meta.textContent = role === "user" ? `Você · ${fmt(now)}` : `ACL · ${fmt(now)}`;
57+
// meta.textContent = role === "user" ? `Você · ${fmt(now)}` : `ACL · ${fmt(now)}`;
58+
meta.textContent = role === "user" ? `Você · ${fmt(now)}` : '';
5859

59-
const breadcrumbs = document.createElement("div");
60-
breadcrumbs.className = "message-breadcrumbs";
61-
if (role === "bot" && !isError) {
62-
setBreadcrumbsContent(breadcrumbs, sources);
63-
} else {
64-
breadcrumbs.hidden = true;
65-
}
60+
// const breadcrumbs = document.createElement("div");
61+
// breadcrumbs.className = "message-breadcrumbs";
62+
// if (role === "bot" && !isError) {
63+
// setBreadcrumbsContent(breadcrumbs, sources);
64+
// } else {
65+
// breadcrumbs.hidden = true;
66+
// }
6667

6768
const bubble = document.createElement("div");
6869
bubble.classList.add("message", role);
@@ -75,7 +76,7 @@ export function appendMessageRow(chatBox, opts) {
7576
}
7677

7778
row.appendChild(meta);
78-
if (role === "bot") row.appendChild(breadcrumbs);
79+
// if (role === "bot") row.appendChild(breadcrumbs);
7980
row.appendChild(bubble);
8081
chatBox.appendChild(row);
8182
scrollBottom();
@@ -90,21 +91,22 @@ export function createStreamingBotRow(chatBox, scrollBottom) {
9091
const row = document.createElement("div");
9192
row.classList.add("message-row", "bot");
9293

93-
const meta = document.createElement("div");
94-
meta.className = "message-meta";
95-
meta.textContent = `ACL · ${fmt(new Date())}`;
94+
// const meta = document.createElement("div");
95+
// meta.className = "message-meta";
96+
// meta.textContent = `ACL · ${fmt(new Date())}`;
9697

97-
const breadcrumbs = document.createElement("div");
98-
breadcrumbs.className = "message-breadcrumbs";
99-
breadcrumbs.hidden = true;
98+
// const breadcrumbs = document.createElement("div");
99+
// breadcrumbs.className = "message-breadcrumbs";
100+
// breadcrumbs.hidden = true;
100101

101102
const bubble = document.createElement("div");
102103
bubble.classList.add("message", "bot", "cursor-blink");
103104

104-
row.appendChild(meta);
105-
row.appendChild(breadcrumbs);
105+
// row.appendChild(meta);
106+
// row.appendChild(breadcrumbs);
106107
row.appendChild(bubble);
107108
chatBox.appendChild(row);
108109
scrollBottom();
109-
return { row, bubble, breadcrumbs };
110+
return { row, bubble };
111+
// return { row, bubble, breadcrumbs };
110112
}

0 commit comments

Comments
 (0)