diff --git a/gitprofile.config.ts b/gitprofile.config.ts index 00e8980..6cbaa7c 100644 --- a/gitprofile.config.ts +++ b/gitprofile.config.ts @@ -3,6 +3,8 @@ const CONFIG = { github: { username: 'IliaDobkin', + /** Keep username for avatar/name API; hide empty public GitHub in the contact card. */ + showInDetails: false, }, base: '/', projects: { @@ -31,26 +33,28 @@ const CONFIG = { { title: 'atlas', description: - 'Python project for infrastructure and tooling.', + 'Python tooling for infrastructure operations, glue scripts, and automation helpers.', language: 'Python', link: 'https://git.levkin.ca/ilia/atlas', }, { title: 'ansible', description: - 'Infrastructure as code — Ansible playbooks and roles for provisioning and configuration.', + 'Infrastructure as code — playbooks and roles for provisioning, configuration, and repeatable environments.', language: 'Ansible', link: 'https://git.levkin.ca/ilia/ansible', }, { title: 'POTE', - description: 'Python project.', + description: + 'Python utilities and experiments — small tools and libraries for day-to-day engineering work.', language: 'Python', link: 'https://git.levkin.ca/ilia/POTE', }, { title: 'mirror_match', - description: 'TypeScript application.', + description: + 'TypeScript app — UI and logic for a focused domain problem; structured for testability.', language: 'TypeScript', link: 'https://git.levkin.ca/ilia/mirror_match', }, @@ -70,7 +74,8 @@ const CONFIG = { }, { title: 'crkl', - description: 'Kotlin application.', + description: + 'Kotlin application — JVM/Android-oriented codebase and patterns.', language: 'Kotlin', link: 'https://git.levkin.ca/ilia/crkl', }, @@ -80,14 +85,15 @@ const CONFIG = { seo: { title: 'Ilia Dobkin — SDET & Test Automation Engineer', description: - 'Software Development Engineer in Test with 20+ years across product, platform, and industrial test automation — Cypress, Playwright, Selenium, CI/CD, accessibility, and regulated domains.', + 'Software Development Engineer in Test with 20+ years across product, platform, and industrial test automation — deep recent experience in regulated iGaming, Cypress, Playwright, Selenium, CI/CD, and accessibility.', imageURL: '', }, location: 'Thornhill, Ontario, Canada', about: [ - 'Driven software engineer with 20+ years of experience spanning product, platform, and industrial test automation — from global audit and financial systems (CaseWare, MNP, JazzIt) to modern web delivery for startups and enterprises alike.', - 'Builds and maintains a self-hosted infrastructure lab (Proxmox, Ansible, Caddy, CI runners) that mirrors production-grade DevOps practices.', - 'Seeking roles where I can provide testing guidance, strengthen CI/CD operations, and collaborate with teams to optimize product delivery.', + '20+ years in product, platform, and industrial test automation — financial and audit software (CaseWare, MNP, JazzIt), regulated iGaming, and modern web for startups and enterprises.', + 'Recent depth in iGaming: operator-facing casino platforms, payments and wallets, responsible gaming and compliance (geo/market rules, age gating, audit-friendly traceability). Automation with Playwright, API and integration tests, performance suites, and CI/CD on PostgreSQL and GCP.', + 'Self-hosted lab (Proxmox, Ansible, Caddy, CI runners) — same discipline I bring to delivery, observability, and reliability at work.', + 'Looking for roles where I shape test strategy, strengthen CI/CD, and help teams ship in regulated, high-availability environments.', ], social: { linkedin: 'idobkin', @@ -108,6 +114,7 @@ const CONFIG = { discord: '', telegram: '', website: 'https://git.levkin.ca', + websiteLabel: 'Git', phone: '647 987 2792', email: 'idobkin@gmail.com', }, @@ -148,7 +155,6 @@ const CONFIG = { 'Cross-browser testing', 'Mobile testing', 'GitHub Actions', - 'GitHub', 'GitLab CI', 'Bitbucket', 'Jenkins', @@ -198,6 +204,7 @@ const CONFIG = { 'DNS', 'Local LLM / GPU', ], + skillsPreviewLimit: 16, experiences: [], certifications: [], educations: [], @@ -212,7 +219,7 @@ const CONFIG = { }, hotjar: { id: '', snippetVersion: 6 }, themeConfig: { - defaultTheme: 'light', + defaultTheme: 'dark', disableSwitch: false, @@ -227,7 +234,7 @@ const CONFIG = { class="text-primary" href="https://git.levkin.ca" target="_blank" rel="noreferrer" - >Gitea · Git · ; + /** + * How many skill badges to show before "Show all" (default 16) + */ + skillsPreviewLimit?: number; + /** * Experience list */ diff --git a/src/components/details-card/index.tsx b/src/components/details-card/index.tsx index a07ddc5..fc3c9ff 100644 --- a/src/components/details-card/index.tsx +++ b/src/components/details-card/index.tsx @@ -77,14 +77,18 @@ const ListItem: React.FC<{ wordBreak: 'break-word', }} > - - {value} - + {link ? ( + + {value} + + ) : ( + {value} + )} ); @@ -147,7 +151,7 @@ const OrganizationItem: React.FC<{ * @param {Object} profile - The profile object. * @param {boolean} loading - Indicates whether the data is loading. * @param {Object} social - The social object. - * @param {Object} github - The GitHub object. + * @param {Object} github - Username API source; showInDetails controls GitHub row visibility. * @return {JSX.Element} The details card component. */ const DetailsCard = ({ profile, loading, social, github }: Props) => { @@ -195,12 +199,14 @@ const DetailsCard = ({ profile, loading, social, github }: Props) => { } /> )} - } - title="GitHub:" - value={github.username} - link={`https://github.com/${github.username}`} - /> + {github.showInDetails && ( + } + title="GitHub:" + value={github.username} + link={`https://github.com/${github.username}`} + /> + )} {social?.researchGate && ( } @@ -324,13 +330,13 @@ const DetailsCard = ({ profile, loading, social, github }: Props) => { {social?.website && ( } - title="Website:" + title={`${social.websiteLabel}:`} value={social.website .replace('https://', '') .replace('http://', '')} link={ !social.website.startsWith('http') - ? `http://${social.website}` + ? `https://${social.website}` : social.website } /> diff --git a/src/components/gitprofile.tsx b/src/components/gitprofile.tsx index 3154bce..9ed8393 100644 --- a/src/components/gitprofile.tsx +++ b/src/components/gitprofile.tsx @@ -201,12 +201,6 @@ const GitProfile = ({ config }: { config: Config }) => { themeConfig={sanitizedConfig.themeConfig} /> )} - {sanitizedConfig.about.length !== 0 && ( - - )} { github={sanitizedConfig.github} social={sanitizedConfig.social} /> + {sanitizedConfig.about.length !== 0 && ( + + )} {sanitizedConfig.resume.fileUrl && sanitizedConfig.resume.previewLines.length !== 0 && ( { )} {sanitizedConfig.certifications.length !== 0 && ( diff --git a/src/components/resume-card/index.tsx b/src/components/resume-card/index.tsx index 2266685..e82471a 100644 --- a/src/components/resume-card/index.tsx +++ b/src/components/resume-card/index.tsx @@ -86,15 +86,25 @@ const ResumeCard = ({

{line}

))} - - View Full Resume (PDF) - + )} diff --git a/src/components/skill-card/index.tsx b/src/components/skill-card/index.tsx index 666ca2e..93a3c6a 100644 --- a/src/components/skill-card/index.tsx +++ b/src/components/skill-card/index.tsx @@ -1,12 +1,19 @@ +import { useState } from 'react'; import { skeleton } from '../../utils'; const SkillCard = ({ loading, skills, + previewLimit, }: { loading: boolean; skills: string[]; + previewLimit: number; }) => { + const [expanded, setExpanded] = useState(false); + const hasMore = skills.length > previewLimit; + const visible = + !hasMore || expanded ? skills : skills.slice(0, previewLimit); const renderSkeleton = () => { const array = []; for (let index = 0; index < 12; index++) { @@ -28,20 +35,38 @@ const SkillCard = ({ {loading ? ( skeleton({ widthCls: 'w-32', heightCls: 'h-8' }) ) : ( - Tech Stack + Tech stack )} + {!loading && hasMore && !expanded && ( +

+ Showing {previewLimit} of {skills.length}. +

+ )}
{loading ? renderSkeleton() - : skills.map((skill, index) => ( + : visible.map((skill, index) => (
{skill}
))}
+ {!loading && hasMore && ( +
+ +
+ )}
diff --git a/src/interfaces/sanitized-config.tsx b/src/interfaces/sanitized-config.tsx index a626761..9c51438 100644 --- a/src/interfaces/sanitized-config.tsx +++ b/src/interfaces/sanitized-config.tsx @@ -1,5 +1,6 @@ export interface SanitizedGithub { username: string; + showInDetails: boolean; } export interface SanitizedGitHubProjects { @@ -60,6 +61,7 @@ export interface SanitizedSocial { dev?: string; stackoverflow?: string; website?: string; + websiteLabel: string; telegram?: string; phone?: string; email?: string; @@ -136,6 +138,7 @@ export interface SanitizedConfig { social: SanitizedSocial; resume: SanitizedResume; skills: Array; + skillsPreviewLimit: number; experiences: Array; educations: Array; certifications: Array; diff --git a/src/utils/index.tsx b/src/utils/index.tsx index b33a03b..2c574c9 100644 --- a/src/utils/index.tsx +++ b/src/utils/index.tsx @@ -45,6 +45,7 @@ export const getSanitizedConfig = ( return { github: { username: config.github.username, + showInDetails: config.github.showInDetails !== false, }, projects: { github: { @@ -93,6 +94,7 @@ export const getSanitizedConfig = ( dev: config?.social?.dev, stackoverflow: config?.social?.stackoverflow, website: config?.social?.website, + websiteLabel: config?.social?.websiteLabel || 'Website', phone: config?.social?.phone, email: config?.social?.email, telegram: config?.social?.telegram, @@ -105,6 +107,10 @@ export const getSanitizedConfig = ( previewLines: config?.resume?.previewLines || [], }, skills: config?.skills || [], + skillsPreviewLimit: Math.max( + 8, + Math.min(64, config?.skillsPreviewLimit ?? 16), + ), experiences: config?.experiences?.filter( (experience) =>