diff --git a/src/pages/ExperiencePage.vue b/src/pages/ExperiencePage.vue index a3982e9..6d51d79 100644 --- a/src/pages/ExperiencePage.vue +++ b/src/pages/ExperiencePage.vue @@ -7,89 +7,389 @@ + + +
+ +
+ 📁 + career/ +
+ + +
-
-
-
-
-
- -
-
- -
+
+ +
+
+ + + +
+
+ +
+
+ + +
+ career + / + {{ visibleExperiences[activeTab]?.slug }}.ts +
+ + +
+
+ +
+ 1 + import + { + Experience + } + from + "@career/types";
-
-

- - {{ experience.company }} - - {{ experience.company }} -

- - {{ experience.position }} - • via {{ getViaName(experience.via) }} - + + +
+ 2 + +
+ + +
+ 3 + /** +
+
+ 4 + + * {{ visibleExperiences[activeTab].position }} @ + {{ visibleExperiences[activeTab].company }}
-
-
- {{ formatDateRange(experience) }} - {{ calculateDuration(experience) }} +
+ {{ 5 + hIndex }} + * - {{ highlight }} +
+
+ {{ + 5 + visibleExperiences[activeTab].highlights.length + }} + + */ +
+ + +
+ {{ + 6 + visibleExperiences[activeTab].highlights.length + }} + export const + experience: + Experience + = + { +
+ + +
+ {{ + 7 + visibleExperiences[activeTab].highlights.length + }} + company: + "{{ visibleExperiences[activeTab].company }}", +
+ + +
+ {{ + 8 + visibleExperiences[activeTab].highlights.length + }} + position: + "{{ visibleExperiences[activeTab].position }}", +
+ + + + + +
+ {{ + (visibleExperiences[activeTab].via ? 10 : 9) + + visibleExperiences[activeTab].highlights.length + }} + period: + { +
+
+ {{ + (visibleExperiences[activeTab].via ? 11 : 10) + + visibleExperiences[activeTab].highlights.length + }} + start: + new + Date("{{ visibleExperiences[activeTab].startDate }}"), +
+
+ {{ + (visibleExperiences[activeTab].via ? 12 : 11) + + visibleExperiences[activeTab].highlights.length + }} + end: + + +
+
+ {{ + (visibleExperiences[activeTab].via ? 13 : 12) + + visibleExperiences[activeTab].highlights.length + }} + duration: + "{{ calculateDuration(visibleExperiences[activeTab]) }}", +
+
+ {{ + (visibleExperiences[activeTab].via ? 14 : 13) + + visibleExperiences[activeTab].highlights.length + }} + },
-
-
    -
  • +
    + {{ + (visibleExperiences[activeTab].via ? 15 : 14) + + visibleExperiences[activeTab].highlights.length + }} + tags: + ["{{ tag }}", + ], +
    + + +
    + {{ + (visibleExperiences[activeTab].via ? 16 : 15) + + visibleExperiences[activeTab].highlights.length + }} + stack: + [ +
    +
    - {{ highlight }} -
  • -
- -
- {{ + (visibleExperiences[activeTab].via ? 17 : 16) + + visibleExperiences[activeTab].highlights.length + + techIndex + }} + "{{ tech }}", +
+
+ {{ + (visibleExperiences[activeTab].via ? 17 : 16) + + visibleExperiences[activeTab].highlights.length + + parseTechnologies(visibleExperiences[activeTab].technologies).length + }} + ], +
+ + +
+ {{ + (visibleExperiences[activeTab].via ? 18 : 17) + + visibleExperiences[activeTab].highlights.length + + parseTechnologies(visibleExperiences[activeTab].technologies).length + }} + website: + "{{ visibleExperiences[activeTab].website }}", +
+ + +
+ {{ + (visibleExperiences[activeTab].via ? 19 : 18) + + visibleExperiences[activeTab].highlights.length + + parseTechnologies(visibleExperiences[activeTab].technologies).length + + (visibleExperiences[activeTab].website ? 1 : 0) + }} + }; +
+
+ + +
+ - - - -
-
+ + +
+
+ TypeScript + UTF-8 +
+
+ Ln {{ 1 }}, Col {{ 1 }} - + Spaces: 2 +
@@ -119,36 +419,18 @@ return companyLogos[experience.slug]; } - // Via company logos and names mapping - const viaLogos: Record = { - toptal: companyLogos["toptal"], - tw: companyLogos["thoughtworks"], - }; - const viaNames: Record = { toptal: "Toptal", tw: "ThoughtWorks", }; - function getViaLogo(via: string): string | undefined { - return viaLogos[via]; - } - function getViaName(via: string): string { return viaNames[via] || via; } const INITIAL_VISIBLE_COUNT = 6; const showAll = ref(false); - - const yearsOfExperience = computed(() => { - const oldestStartDate = experiencesConfig - .map((exp) => new Date(exp.startDate).getTime()) - .sort((a, b) => a - b)[0]; - const startYear = new Date(oldestStartDate).getFullYear(); - const currentYear = new Date().getFullYear(); - return currentYear - startYear; - }); + const activeTab = ref(0); const visibleExperiences = computed(() => { if (showAll.value) { @@ -157,6 +439,13 @@ return experiencesConfig.slice(0, INITIAL_VISIBLE_COUNT); }); + function closeTab(index: number) { + if (visibleExperiences.value.length <= 1) return; + if (activeTab.value >= index && activeTab.value > 0) { + activeTab.value--; + } + } + const RECENT_YEARS = 5; const recentTechnologies = computed(() => { @@ -172,45 +461,16 @@ } }); - return Array.from(techSet).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); + return Array.from(techSet).sort((a, b) => + a.toLowerCase().localeCompare(b.toLowerCase()) + ); }); - function formatDateRange(experience: Experience): string { - const startDate = new Date(experience.startDate); - const startStr = formatMonth(startDate); - - if (!experience.endDate) { - return `${startStr} - present`; - } - - const endDate = new Date(experience.endDate); - const endStr = formatMonth(endDate); - return `${startStr} - ${endStr}`; - } - - function formatMonth(date: Date): string { - const months = [ - "jan", - "feb", - "mar", - "apr", - "may", - "jun", - "jul", - "aug", - "sep", - "oct", - "nov", - "dec", - ]; - return `${months[date.getMonth()]}/${date.getFullYear()}`; - } - function calculateDuration(experience: Experience): string { const startDate = new Date(experience.startDate); const endDate = experience.endDate ? new Date(experience.endDate) : new Date(); - let months = + const months = (endDate.getFullYear() - startDate.getFullYear()) * 12 + (endDate.getMonth() - startDate.getMonth()); @@ -219,20 +479,16 @@ let duration = ""; if (years > 0) { - duration += `${years} ${years === 1 ? "year" : "years"}`; + duration += `${years} year${years > 1 ? "s" : ""}`; } if (remainingMonths > 0) { if (duration) duration += ", "; - duration += `${remainingMonths} ${remainingMonths === 1 ? "month" : "months"}`; + duration += `${remainingMonths} month${remainingMonths > 1 ? "s" : ""}`; } if (!duration) { duration = "< 1 month"; } - if (!experience.endDate) { - duration += " (and counting)"; - } - return duration; } @@ -249,11 +505,20 @@ .page-layout { display: flex; - gap: var(--space-8); + gap: var(--space-6); max-width: 1200px; margin: 0 auto; } + .page-title { + margin-bottom: var(--space-8); + text-align: center; + max-width: 1200px; + margin-left: auto; + margin-right: auto; + } + + /* Sidebar */ .sidebar { position: sticky; top: calc(56px + var(--space-8)); @@ -283,217 +548,346 @@ margin: 0 0 var(--space-4) 0; } - .content { - flex: 1; - max-width: var(--content-max-width); + /* File tree */ + .file-tree { + margin-top: var(--space-6); + padding-top: var(--space-4); + border-top: 1px solid var(--color-border); } - .page-title { - margin-bottom: var(--space-8); - text-align: center; - max-width: 1200px; - margin-left: auto; - margin-right: auto; + .tree-folder { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-1) 0; + font-family: var(--font-mono); + font-size: var(--text-sm); + color: var(--color-text-primary); + } + + .folder-icon { + font-size: 14px; } - .timeline { + .folder-name { + font-weight: var(--font-semibold); + } + + .tree-file { display: flex; - flex-direction: column; - gap: var(--space-8); + align-items: center; + gap: var(--space-2); + width: 100%; + padding: var(--space-1) var(--space-1) var(--space-1) var(--space-4); + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-text-secondary); + background: none; + border: none; + border-radius: var(--radius-sm); + cursor: pointer; + text-align: left; + transition: background-color var(--transition-fast); } - .experience-card { - background: var(--color-surface); + .tree-file:hover { + background: var(--color-surface-hover); + } + + .tree-file.active { + background: var(--color-primary-light); + color: var(--color-primary); + } + + .tree-file.more-files { + color: var(--color-text-muted); + font-style: italic; + } + + .file-icon { + font-size: 12px; + opacity: 0.7; + } + + .file-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + /* Content */ + .content { + flex: 1; + min-width: 0; + } + + /* Code editor */ + .code-editor { + background: var(--color-background-elevated); + border: 1px solid var(--color-border); border-radius: var(--radius-lg); - padding: var(--space-6); - box-shadow: var(--shadow-md); - transition: background-color var(--transition-theme), box-shadow var(--transition-theme); + overflow: hidden; + box-shadow: var(--shadow-lg); } - .experience-header { + .editor-header { display: flex; - justify-content: space-between; - align-items: flex-start; - gap: var(--space-4); - margin-bottom: var(--space-4); - padding-bottom: var(--space-4); + align-items: center; + background: var(--color-surface); border-bottom: 1px solid var(--color-border); } - .logos-wrapper { + .window-controls { display: flex; - align-items: center; - gap: var(--space-2); + gap: 8px; + padding: var(--space-3) var(--space-4); flex-shrink: 0; } - .company-logo-wrapper { - flex-shrink: 0; + .control { + width: 12px; + height: 12px; + border-radius: var(--radius-full); + background: var(--color-border); } - .company-logo { - width: 58px; - height: 58px; - border-radius: var(--radius-md); - object-fit: cover; + .control.close { + background: #ff5f57; } - .via-logo-wrapper { - flex-shrink: 0; + .control.minimize { + background: #febc2e; } - .via-logo { - width: 32px; - height: 32px; - border-radius: var(--radius-sm); - object-fit: cover; - opacity: 0.8; + .control.maximize { + background: #28c840; } - .experience-title { + .editor-tabs { display: flex; - flex-direction: column; - gap: var(--space-1); flex: 1; + overflow-x: auto; + scrollbar-width: none; } - .company-name { + .editor-tabs::-webkit-scrollbar { + display: none; + } + + .tab { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); font-family: var(--font-mono); - font-size: var(--text-xl); - font-weight: var(--font-semibold); - margin: 0; + font-size: var(--text-xs); + color: var(--color-text-muted); + background: none; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + white-space: nowrap; + transition: + color var(--transition-fast), + background-color var(--transition-fast); + } + + .tab:hover { + color: var(--color-text-secondary); + background: var(--color-surface-hover); + } + + .tab.active { color: var(--color-text-primary); - text-transform: lowercase; + background: var(--color-background-elevated); + border-bottom-color: var(--color-primary); } - .company-name::before { - content: "// "; - color: var(--color-primary); + .tab-icon { + font-size: 10px; + padding: 1px 3px; + background: var(--color-primary); + color: white; + border-radius: 2px; + font-weight: var(--font-bold); } - .company-link { - color: inherit; - text-decoration: none; - transition: color var(--transition-fast); + .tab-name { + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; } - .company-link:hover { - color: var(--color-primary); + .tab-close { + opacity: 0; + font-size: 14px; + line-height: 1; + padding: 0 2px; + border-radius: 2px; + transition: opacity var(--transition-fast); + } + + .tab:hover .tab-close { + opacity: 0.5; + } + + .tab-close:hover { + opacity: 1 !important; + background: var(--color-surface-active); } - .position { + .editor-breadcrumb { + display: flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-2) var(--space-4); font-family: var(--font-mono); - font-size: var(--text-base); - color: var(--color-primary); - text-transform: lowercase; - font-weight: var(--font-semibold); + font-size: var(--text-xs); + color: var(--color-text-muted); + background: var(--color-background-elevated); + border-bottom: 1px solid var(--color-border-subtle); } - .via-text { + .breadcrumb-sep { + opacity: 0.5; + } + + .breadcrumb-item.active { + color: var(--color-text-secondary); + } + + .editor-content { + position: relative; + padding: var(--space-4) 0; + min-height: 400px; + overflow-x: auto; + } + + .code-block { font-family: var(--font-mono); font-size: var(--text-sm); - color: var(--color-text-muted); - text-transform: lowercase; + line-height: 1.6; } - .experience-meta { + .line { display: flex; - flex-direction: column; - align-items: flex-end; - gap: var(--space-1); + padding: 0 var(--space-4); + min-height: 1.6em; + } + + .line:hover { + background: var(--color-surface-hover); + } + + .line-number { + color: var(--color-text-muted); + opacity: 0.5; + min-width: 40px; text-align: right; + padding-right: var(--space-4); + user-select: none; + flex-shrink: 0; } - .date-info { - display: flex; - flex-direction: column; - align-items: flex-end; - gap: var(--space-1); + .line code { + white-space: pre-wrap; + word-break: break-word; } - .date-range { - font-family: var(--font-mono); - font-size: var(--text-sm); - color: var(--color-text-secondary); - text-transform: lowercase; + .line code.indent-1 { + padding-left: 2ch; } - .duration { - font-family: var(--font-mono); - font-size: var(--text-xs); + .line code.indent-2 { + padding-left: 4ch; + } + + /* Syntax highlighting */ + .code-keyword { + color: var(--color-purple); + } + + .code-type { + color: var(--color-teal); + } + + .code-var { + color: var(--quadrant-NE); + } + + .code-prop { + color: var(--quadrant-NE); + } + + .code-string { + color: var(--color-green); + } + + .code-punct { color: var(--color-text-muted); } - .highlights { - list-style: none; - padding: 0; - margin: 0 0 var(--space-4) 0; - display: flex; - flex-direction: column; - gap: var(--space-2); + .code-comment { + color: var(--color-text-muted); + font-style: italic; } - .highlight-item { - font-family: var(--font-sans); - font-size: var(--text-base); - color: var(--color-text-secondary); - line-height: var(--leading-relaxed); - padding-left: var(--space-4); - position: relative; + .code-link { + color: inherit; + text-decoration: underline; + text-decoration-style: dotted; } - .highlight-item::before { - content: ">"; - position: absolute; - left: 0; + .code-link:hover { color: var(--color-primary); - font-family: var(--font-mono); - font-weight: var(--font-bold); } - .experience-footer { - padding-top: var(--space-4); - border-top: 1px solid var(--color-border); + /* Logo watermark */ + .logo-watermark { + position: absolute; + bottom: var(--space-4); + right: var(--space-4); + opacity: 0.08; + pointer-events: none; } - .show-more-container { - display: flex; - justify-content: center; - padding: var(--space-6) 0; + .logo-watermark img { + width: 120px; + height: 120px; + border-radius: var(--radius-lg); + object-fit: cover; } - .show-more-button { + /* Status bar */ + .editor-statusbar { display: flex; - flex-direction: column; - align-items: flex-start; - gap: var(--space-1); + justify-content: space-between; + padding: var(--space-1) var(--space-4); + background: var(--color-primary); font-family: var(--font-mono); - font-size: var(--text-sm); - color: var(--color-text-secondary); - background: none; - border: none; - cursor: pointer; - padding: var(--space-4); - text-transform: lowercase; - transition: color var(--transition-fast); - } - - .show-more-button:hover { - color: var(--color-text-primary); + font-size: var(--text-xs); + color: white; } - .show-more-button .comment-prefix { - color: var(--color-primary); + .status-left, + .status-right { + display: flex; + gap: var(--space-4); } - .show-more-line { - display: block; + .status-item { + opacity: 0.9; } + /* Responsive */ @media (--lg) { .sidebar { width: 180px; } + + .tab-name { + max-width: 80px; + } } @media (--md) { @@ -510,42 +904,29 @@ width: 100%; } - .experience-header { - flex-wrap: wrap; - gap: var(--space-3); - } - - .company-logo { - width: 50px; - height: 50px; - } - - .via-logo { - width: 26px; - height: 26px; + .file-tree { + display: none; } - .experience-meta { - align-items: flex-start; - text-align: left; + .window-controls { + display: none; } - .date-info { - flex-direction: row; - align-items: baseline; - gap: var(--space-3); + .tab-name { + max-width: 60px; } - .duration::before { - content: "• "; + .line-number { + min-width: 30px; + padding-right: var(--space-2); } - .company-name { - font-size: var(--text-lg); + .code-block { + font-size: var(--text-xs); } - .experience-card { - padding: var(--space-4); + .logo-watermark { + display: none; } }