feat(xp): add terminal-style ui for experience timeline#32
feat(xp): add terminal-style ui for experience timeline#32
Conversation
Replace the traditional card-based timeline with a terminal-themed presentation to show career entries in a command-output style. This introduces a .terminal container, header (window controls and title), prompt/output lines, ASCII timeline connectors, compact formatted fields (role, period, tags, highlights, tech stack, URL), and a show-more interaction that reveals remaining entries. Also update computed properties (totalCompanies, remainingCount), utilities (formatting, duration, tag classes), and various CSS styles to support the terminal UI and responsive behavior. The change was needed to provide an alternative terminal UI option requested in the prompt, offering a compact, readable, and themed way to browse experiences while keeping existing data (highlights, technologies, websites) available in a CLI-like layout.
WalkthroughThe ExperiencePage.vue component undergoes a comprehensive visual redesign, transitioning from a timeline-based layout to a terminal-style UI. The changes include new computed properties for company statistics, helper functions for date/tag/technology formatting, restructured template rendering, and extensive CSS styling to achieve the terminal aesthetic. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20–25 minutes Areas requiring attention:
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/pages/ExperiencePage.vue (1)
387-440: Consider whether hard-coded terminal colors align with maintainability goals.The terminal UI uses hard-coded hex colors (#1a1a1a, #2d2d2d, #50fa7b, etc.) rather than CSS custom properties like
var(--color-*). This ensures a consistent terminal theme across light and dark modes (confirmed by lines 748-754), but reduces maintainability if colors need to change. If the terminal should always use a fixed Dracula-style palette regardless of site theme, this is acceptable; otherwise, consider abstracting key colors to custom properties.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/pages/ExperiencePage.vue(8 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.vue: Indent content inside<script>and<style>tags in Vue files (configured viavueIndentScriptAndStyle: truein.prettierrc)
Use Vue 3 Composition API with<script setup>syntax for Vue components
D3 integration should use imperative DOM manipulation within Vue lifecycle hooks, kept separate from Vue reactivity
Files:
src/pages/ExperiencePage.vue
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use constants:
IDEAL_BLIP_WIDTH = 22px,NEW_GROUP_BLIP_WIDTH = 88px,EXISTING_GROUP_BLIP_WIDTH = 124px
Files:
src/pages/ExperiencePage.vue
🧠 Learnings (1)
📚 Learning: 2025-12-14T01:59:00.938Z
Learnt from: CR
Repo: caiokf/homepage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-14T01:59:00.938Z
Learning: Applies to **/*.{ts,tsx,vue} : Use constants: `IDEAL_BLIP_WIDTH = 22px`, `NEW_GROUP_BLIP_WIDTH = 88px`, `EXISTING_GROUP_BLIP_WIDTH = 124px`
Applied to files:
src/pages/ExperiencePage.vue
🔇 Additional comments (8)
src/pages/ExperiencePage.vue (8)
13-22: LGTM!The terminal header effectively establishes the terminal aesthetic with window controls and a command-style title.
199-206: LGTM!The via name mapping handles unknown values gracefully with a fallback to the original value.
211-218: LGTM!The years of experience calculation correctly finds the oldest start date and computes the difference from the current year.
229-254: LGTM!The computed properties correctly handle visible experiences and recent technologies filtering. The case-insensitive sorting ensures consistent technology display.
287-327: LGTM!The duration calculation, technology parsing, and tag classification functions are implemented correctly. The compact duration format (e.g., "3y 4m", "<1m") fits the terminal aesthetic well.
490-663: LGTM!The experience block and detail line styles comprehensively support the terminal UI layout with appropriate visual hierarchy and color coding.
701-755: LGTM!The responsive styles appropriately scale the terminal UI for different screen sizes. The custom media queries (
--md,--lg) assume proper configuration elsewhere in the build setup.
256-285: No error handling needed for date formatting.All dates in
experiencesConfiguse valid ISO 8601 format (YYYY-MM-DD). The data is hardcoded and consistently formatted, sonew Date()will always produce valid date objects. Error handling for invalid dates is unnecessary in this context.Likely an incorrect or invalid review comment.
| <div class="timeline-connector"> | ||
| <span v-if="index === 0" class="connector-char">┌</span> | ||
| <span v-else class="connector-char">├</span> | ||
| <span class="connector-line">────</span> | ||
| <span class="connector-node" :class="{ current: !experience.endDate }">{{ | ||
| !experience.endDate ? "◉" : "○" | ||
| }}</span> | ||
| <span class="connector-line">──</span> | ||
| </div> |
There was a problem hiding this comment.
Consider adding aria-hidden to decorative ASCII timeline characters.
The ASCII timeline connectors (┌, ├, ○, ◉, etc.) are purely decorative but may be announced by screen readers in confusing ways. Consider wrapping the timeline-connector divs or the individual spans with aria-hidden="true" to improve accessibility.
Example:
-<div class="timeline-connector">
+<div class="timeline-connector" aria-hidden="true">🤖 Prompt for AI Agents
In src/pages/ExperiencePage.vue around lines 61-69, the ASCII timeline
characters are decorative and should be hidden from assistive tech; add
aria-hidden="true" to the timeline-connector container or to each decorative
span (connector-char, connector-line, connector-node) so screen readers skip
them, and ensure any semantic status (e.g., current vs ended) is exposed
separately to AT via an accessible label or a visually-hidden element on the
experience item.
| <div v-if="!showAll" class="show-more-block"> | ||
| <div class="timeline-connector"> | ||
| <span class="connector-char">│</span> | ||
| </div> | ||
| <div class="output-line"> | ||
| <span class="output-dim">... {{ remainingCount }} more entries hidden ...</span> | ||
| </div> | ||
| <div class="output-line blank"></div> | ||
| <div class="prompt-line"> | ||
| <span class="prompt">$</span> | ||
| <button @click="showAll = true" class="show-more-cmd"> | ||
| career --list --all | ||
| <span class="cursor">▋</span> | ||
| </button> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Guard against showing the block when remainingCount ≤ 0.
If the total number of experiences is less than or equal to INITIAL_VISIBLE_COUNT, remainingCount (line 225) will be zero or negative, yet the show-more block will still display with a confusing message like "... 0 more entries hidden ...".
Apply this diff to fix the condition:
-<div v-if="!showAll" class="show-more-block">
+<div v-if="!showAll && remainingCount > 0" class="show-more-block">🤖 Prompt for AI Agents
In src/pages/ExperiencePage.vue around lines 156 to 171, the "show-more" block
is rendered even when remainingCount is 0 or negative; change the v-if to guard
both conditions (only render when not showing all AND remainingCount > 0).
Update the template condition from v-if="!showAll" to v-if="!showAll &&
remainingCount > 0" so the block is hidden when there are no additional entries
to reveal.
| .cursor { | ||
| color: #50fa7b; | ||
| animation: blink 1s step-end infinite; | ||
| } | ||
|
|
||
| .show-more-line { | ||
| display: block; | ||
| @keyframes blink { | ||
| 0%, | ||
| 100% { | ||
| opacity: 1; | ||
| } | ||
| 50% { | ||
| opacity: 0; | ||
| } | ||
| } |
There was a problem hiding this comment.
Respect prefers-reduced-motion for the blinking cursor.
The blinking cursor animation should pause for users who have requested reduced motion to improve accessibility.
Apply this diff:
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
+
+@media (prefers-reduced-motion: reduce) {
+ .cursor {
+ animation: none;
+ }
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .cursor { | |
| color: #50fa7b; | |
| animation: blink 1s step-end infinite; | |
| } | |
| .show-more-line { | |
| display: block; | |
| @keyframes blink { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0; | |
| } | |
| } | |
| .cursor { | |
| color: #50fa7b; | |
| animation: blink 1s step-end infinite; | |
| } | |
| @keyframes blink { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0; | |
| } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .cursor { | |
| animation: none; | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/pages/ExperiencePage.vue around lines 686 to 699, the .cursor blinking
animation does not respect users' prefers-reduced-motion setting; add a CSS
media query to disable the animation for users who prefer reduced motion (e.g.
@media (prefers-reduced-motion: reduce) { .cursor { animation: none; } }) so the
cursor does not blink when reduced motion is requested.
Replace the traditional card-based timeline with a terminal-themed presentation to show career entries in a command-output style. This introduces a .terminal container, header (window controls and title), prompt/output lines, ASCII timeline connectors, compact formatted fields (role, period, tags, highlights, tech stack, URL), and a show-more interaction that reveals remaining entries. Also update computed properties (totalCompanies, remainingCount), utilities (formatting, duration, tag classes), and various CSS styles to support the terminal UI and responsive behavior.
The change was needed to provide an alternative terminal UI option requested in the prompt, offering a compact, readable, and themed way to browse experiences while keeping existing data (highlights, technologies, websites) available in a CLI-like layout.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.