-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
- import
+ {
+ Experience
+ }
+ from
+ "@career/types";
- = {
- 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;
}
}
- - {{ experience.company }} - - {{ experience.company }} -
- - {{ experience.position }} - • via {{ getViaName(experience.via) }} - + + +
+ 2
+
+
+
+
+
+ 3
+
+ /**
+
+ 4
+
-
@@ -119,36 +419,18 @@
return companyLogos[experience.slug];
}
- // Via company logos and names mapping
- const viaLogos: Record
+ * {{ visibleExperiences[activeTab].position }} @
+ {{ visibleExperiences[activeTab].company }}