diff --git a/.codex/skills/github-pages-academic-portfolio/SKILL.md b/.codex/skills/github-pages-academic-portfolio/SKILL.md new file mode 100644 index 0000000..2a8b2af --- /dev/null +++ b/.codex/skills/github-pages-academic-portfolio/SKILL.md @@ -0,0 +1,53 @@ +--- +name: github-pages-academic-portfolio +description: Design, audit, optimize, and maintain evidence-based academic or technical portfolios hosted on GitHub Pages, especially Jekyll and al-folio sites. Use for portfolio information architecture, visual systems, responsive components, accessibility, SEO, performance, GitHub Actions, or pull-request delivery; do not use for unrelated application development. +--- + +# GitHub Pages Academic Portfolio + +Build a portfolio that makes the person's research direction, technical capability, and evidence easy to understand. Treat visual polish, accessibility, content integrity, and maintainability as one system. + +## Start from repository evidence + +1. Inspect the repository root, current branch, status, remote, theme/runtime version, page collection, layouts, styles, build workflow, and existing content conventions. +2. Preserve unrelated work and existing URLs. Prefer a focused feature branch. +3. Identify which files are owned locally and which are supplied by the theme or a gem. Prefer additive site-level layouts and partials to large upstream overrides. If an override is necessary, document why and keep it narrow. +4. Treat profile and CV claims as user-provided evidence. Do not invent publications, affiliations, metrics, credentials, project outcomes, or proficiency levels. +5. Never publish private source documents, salary data, personal addresses, signatures, identification numbers, or contract administration details. + +## Choose the smallest useful mode + +- **Audit:** assess information hierarchy, visual consistency, responsiveness, accessibility, SEO, performance, content evidence, and deployment reliability. +- **Design system:** define tokens, typography, spacing, surfaces, focus states, cards, navigation, buttons, and light/dark behavior before styling individual pages. +- **Portfolio implementation:** improve the homepage and relevant pages while preserving validated content and stable permalinks. +- **Content refinement:** structure each project around problem, method, validation, result, limitations, and resources. Separate verified outcomes from planned work. +- **Delivery:** validate locally, prepare a focused commit, and create or update a pull request only when remote writes are authorized. + +Read [design system](references/design-system.md) before selecting colors or changing site-wide styling. Read [content and SEO](references/content-and-seo.md) when changing page structure, professional positioning, metadata, or project narratives. Read [validation](references/validation.md) before declaring the work complete. + +## Implementation rules + +- Use semantic HTML and native navigation landmarks. Keep one clear page heading and a logical heading hierarchy. +- Centralize shared visual rules. Do not duplicate a full design system inside individual page files. +- Use CSS custom properties as the public token layer. Define both light and dark values and keep component rules token-driven. +- Keep body copy comfortable at 16px-equivalent or larger in the browser. Limit prose width to roughly 65–75 characters. +- Use responsive grids that collapse without horizontal scrolling. Verify at mobile, tablet, desktop, and 200% text zoom. +- Make link, button, hover, active, and keyboard-focus states visibly distinct. Do not communicate meaning through color alone. +- Prefer restrained motion. Respect `prefers-reduced-motion` and avoid decorative animation that delays content. +- Optimize images with correct dimensions, descriptive alt text, and lazy loading except for the primary above-the-fold profile image. +- Keep external links explicit and safe. Use `rel="noopener noreferrer"` for new-tab links. +- Preserve working dark mode, search, CV download, structured metadata, canonical URLs, and deployment paths. + +## Quality gates + +Before handoff: + +1. Run the repository formatter and production build. +2. Run `scripts/audit_portfolio.py REPOSITORY_ROOT` from this skill. +3. Check every foreground/background pair with `scripts/check_contrast.py`; normal text must reach 4.5:1 and large text or essential UI graphics 3:1. +4. Inspect the rendered homepage and every materially changed page at desktop and mobile widths, in light and dark modes. +5. Verify keyboard navigation, visible focus, image alt text, headings, landmarks, internal links, CV download, and external profile links. +6. Review the full branch diff for private information, unrelated files, generated clutter, and unsupported claims. +7. Report checks actually run, remaining limitations, files changed, and remote actions performed. + +Do not describe a CI configuration as passing until the corresponding run completes successfully. If a production build cannot run locally, state that clearly and require the pull-request build to pass before recommending merge. diff --git a/.codex/skills/github-pages-academic-portfolio/agents/openai.yaml b/.codex/skills/github-pages-academic-portfolio/agents/openai.yaml new file mode 100644 index 0000000..56fa1d5 --- /dev/null +++ b/.codex/skills/github-pages-academic-portfolio/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Academic Portfolio" + short_description: "Design and optimize an academic GitHub portfolio" + brand_color: "#0B5CAD" + default_prompt: "Use $github-pages-academic-portfolio to improve my GitHub Pages academic portfolio." diff --git a/.codex/skills/github-pages-academic-portfolio/references/content-and-seo.md b/.codex/skills/github-pages-academic-portfolio/references/content-and-seo.md new file mode 100644 index 0000000..0e715dc --- /dev/null +++ b/.codex/skills/github-pages-academic-portfolio/references/content-and-seo.md @@ -0,0 +1,46 @@ +# Portfolio content and SEO + +## Information architecture + +The homepage should answer, in order: + +1. Who is this person professionally? +2. What problems and research areas do they work on now? +3. What evidence demonstrates their capability? +4. Where can a reviewer inspect projects, research, CV, code, or contact details? + +Keep the primary navigation small. Prefer stable sections such as About, Research, Projects, CV, and Contact. Avoid duplicate labels and near-duplicate pages. + +## Academic and technical credibility + +- State the current academic or research role precisely. +- Distinguish active research, completed work, and future interests. +- Present open-source contributions as contributions, not as ownership of upstream software. +- Do not call work peer reviewed, published, deployed, production-grade, or state of the art without evidence. +- Use measured results only when the evaluation design and comparison context are available. +- For machine-learning projects, disclose validation strategy and important generalization limitations. + +## Project narrative + +Use this order when the evidence supports it: + +- **Problem:** the technical or scientific question. +- **Method:** data, models, software, and workflow. +- **Validation:** splits, baselines, cross-validation, reproducibility, or parity checks. +- **Result:** concise quantitative or qualitative outcome. +- **Limitation:** the main boundary on interpretation. +- **Resources:** repository, report, demonstration, or publication. + +## Search and social metadata + +- Give every indexable page a unique title and accurate description. +- Keep canonical URLs and sitemap generation enabled. +- Provide Open Graph metadata and a valid share image where supported. +- Use descriptive link text instead of repeated “click here”. +- Use one H1 per page and a logical H2/H3 hierarchy. +- Keep URLs stable during redesigns; add redirects only when a URL must change. +- Include person and website structured data only when fields are accurate and publicly intended. + +## Privacy + +Publish professional contact channels intentionally selected by the user. Do not infer or expose private email addresses, phone numbers, home addresses, employment contract details, salary information, signatures, or identifiers from source documents. diff --git a/.codex/skills/github-pages-academic-portfolio/references/design-system.md b/.codex/skills/github-pages-academic-portfolio/references/design-system.md new file mode 100644 index 0000000..159c563 --- /dev/null +++ b/.codex/skills/github-pages-academic-portfolio/references/design-system.md @@ -0,0 +1,52 @@ +# Academic portfolio design system + +## Visual direction + +Use a restrained institutional palette rather than a high-saturation product-marketing palette. A reliable pattern is: + +- deep navy for authority, headings, and the primary dark surface; +- accessible blue for links and primary actions; +- teal for scientific or research emphasis; +- warm gold only for small decorative accents, never as low-contrast body text; +- cool off-white and blue-grey neutrals for reading surfaces and borders. + +The palette should support the content hierarchy, not become the subject of the page. Use one dominant accent and one secondary accent per component. + +## Recommended token model + +Define semantic tokens rather than embedding colors in components: + +- `--global-bg-color`, `--global-card-bg-color`, `--global-text-color`, `--global-text-color-light`; +- `--global-theme-color`, `--global-hover-color`, `--global-divider-color` for theme compatibility; +- site tokens such as `--academic-navy`, `--academic-teal`, `--academic-gold`, `--academic-surface-soft`, `--academic-shadow`, and `--academic-focus`; +- spacing, radius, maximum reading width, and transition tokens. + +Every token that changes meaning between light and dark modes needs an explicit dark value. Component rules should consume tokens and should not repeat raw hex values except for fixed transparent overlays. + +## Typography + +- Prefer a neutral humanist sans serif for navigation and body text. +- A restrained serif may be used for major academic headings if it remains readable and loads efficiently. +- Keep line height around 1.55–1.75 for prose and 1.1–1.3 for headings. +- Use weight, size, spacing, and structure together; do not rely only on color. +- Avoid justified body text and overly narrow columns. + +## Components + +- **Navigation:** quiet surface, strong active state, adequate targets, stable mobile collapse, and clear focus rings. +- **Hero:** one career thesis, one short supporting paragraph, two primary actions at most, then compact evidence or focus labels. +- **Cards:** subtle border and shadow, consistent radii, no excessive hover lift, and no interaction styling on non-clickable cards. +- **Project evidence:** expose method, validation, result, limitation, and source links without burying them in decorative UI. +- **Calls to action:** primary action uses the accessible blue; secondary action uses a bordered surface. Both require hover and focus states. +- **Photography:** use one professional profile image with a quiet frame; do not add generic stock photography to technical portfolio pages. + +## Accessibility invariants + +- Normal text contrast: at least 4.5:1. +- Large text and essential non-text UI contrast: at least 3:1. +- Focus indicator: visible at 2px or stronger and distinguishable from the component and its surrounding surface. +- Target size: aim for at least 44px in the primary navigation and calls to action. +- Motion: disable non-essential transforms and transitions under `prefers-reduced-motion: reduce`. +- Zoom/reflow: content must remain usable at 200% text zoom and narrow mobile widths. + +Use `scripts/check_contrast.py` for deterministic color-pair checks before relying on visual judgement. diff --git a/.codex/skills/github-pages-academic-portfolio/references/validation.md b/.codex/skills/github-pages-academic-portfolio/references/validation.md new file mode 100644 index 0000000..f33bf6e --- /dev/null +++ b/.codex/skills/github-pages-academic-portfolio/references/validation.md @@ -0,0 +1,49 @@ +# Validation and delivery + +## Local checks + +Run the repository's own commands first. For a typical Jekyll portfolio: + +```bash +npm ci +npm run lint:prettier +JEKYLL_ENV=production bundle exec jekyll build +``` + +Then run the skill checks: + +```bash +python SKILL_ROOT/scripts/audit_portfolio.py REPOSITORY_ROOT +python SKILL_ROOT/scripts/check_contrast.py --pair "body:#172033:#f7f9fc:4.5" --pair "link:#0b5cad:#ffffff:4.5" +``` + +Use the actual skill and repository paths; do not copy the placeholders literally. + +## Rendered review + +Inspect at minimum: + +- homepage, Research, Projects, CV, and Contact; +- 375px, 768px, and wide desktop viewports; +- light and dark themes; +- keyboard-only navigation and visible focus; +- text zoom or browser zoom at 200%; +- profile image, internal links, project links, social profiles, and CV download; +- heading order, landmarks, accessible names, and alt text; +- overflow, clipping, awkward line breaks, layout shift, and insufficient card spacing. + +When available, run Lighthouse or an equivalent accessibility/performance audit against the production build. Treat automated scores as signals, not proof of usability. + +## Pull request + +Keep the branch focused. The pull request should explain: + +- the design and information-hierarchy problem; +- the new token system and major component changes; +- content or URL changes, if any; +- accessibility, responsive, performance, and build validation; +- theme overrides and future-upgrade implications; +- screenshots or preview artifacts when available; +- rollback, normally reverting the focused merge commit. + +Wait for required checks. Fix failures on the same branch, rerun validation, and update the pull request rather than opening replacement pull requests for the same change. diff --git a/.codex/skills/github-pages-academic-portfolio/scripts/audit_portfolio.py b/.codex/skills/github-pages-academic-portfolio/scripts/audit_portfolio.py new file mode 100644 index 0000000..d6d170a --- /dev/null +++ b/.codex/skills/github-pages-academic-portfolio/scripts/audit_portfolio.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Run dependency-free structural checks for a Jekyll academic portfolio.""" + +from __future__ import annotations + +import argparse +import re +import sys +from collections import Counter +from pathlib import Path + + +FRONT_MATTER = re.compile(r"\A---\s*\n(?P
.*?)\n---\s*\n", re.DOTALL) +FIELD = re.compile(r"^(?P[A-Za-z_][\w-]*):\s*(?P.*?)\s*$") +ROOT_LINK = re.compile(r"(?:href=[\"']|\]\()(?P/[^\"')#?\s]*)(?:#[^\"')\s]*)?") + + +def front_matter(path: Path) -> dict[str, str]: + text = path.read_text(encoding="utf-8") + match = FRONT_MATTER.match(text) + if not match: + raise ValueError("missing YAML front matter") + fields: dict[str, str] = {} + for line in match.group("header").splitlines(): + field = FIELD.match(line) + if field: + fields[field.group("key")] = field.group("value").strip('"\'') + return fields + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("repository", nargs="?", default=".", type=Path) + args = parser.parse_args() + root = args.repository.resolve() + errors: list[str] = [] + warnings: list[str] = [] + + required = ("_config.yml", "Gemfile", "_pages", ".github/workflows/deploy.yml") + for relative in required: + if not (root / relative).exists(): + errors.append(f"missing required path: {relative}") + + pages: list[tuple[Path, dict[str, str]]] = [] + for path in sorted((root / "_pages").glob("*.md")): + try: + fields = front_matter(path) + except ValueError as error: + errors.append(f"{path.relative_to(root)}: {error}") + continue + pages.append((path, fields)) + if not fields.get("title"): + errors.append(f"{path.relative_to(root)}: missing title") + if not fields.get("permalink"): + warnings.append(f"{path.relative_to(root)}: missing explicit permalink") + + permalinks = [fields.get("permalink") for _, fields in pages if fields.get("permalink")] + for permalink, count in Counter(permalinks).items(): + if count > 1: + errors.append(f"duplicate permalink {permalink!r} appears {count} times") + + nav_titles = [ + fields.get("title", "").casefold() + for _, fields in pages + if fields.get("nav", "").casefold() == "true" + ] + for title, count in Counter(nav_titles).items(): + if title and count > 1: + errors.append(f"duplicate navigation title {title!r} appears {count} times") + + known_routes = set(permalinks) + known_routes.add("/") + for path, _ in pages: + text = path.read_text(encoding="utf-8") + for match in ROOT_LINK.finditer(text): + route = match.group("path") + if route.startswith("//") or "." in Path(route).name: + continue + normalized = route if route.endswith("/") else f"{route}/" + if normalized not in known_routes: + warnings.append(f"{path.relative_to(root)}: unresolved root-relative route {route}") + + profile = root / "assets/img/sabuj.jpg" + if not profile.exists(): + warnings.append("expected profile image assets/img/sabuj.jpg was not found") + + for path in root.rglob("*.md"): + if any(part in {".git", "node_modules", "tmp", "vendor"} for part in path.parts): + continue + text = path.read_text(encoding="utf-8", errors="replace").casefold() + if "lorem ipsum" in text or "[todo" in text: + errors.append(f"placeholder content remains in {path.relative_to(root)}") + + for item in errors: + print(f"ERROR: {item}") + for item in sorted(set(warnings)): + print(f"WARN: {item}") + print(f"Audited {len(pages)} page files: {len(errors)} error(s), {len(set(warnings))} warning(s)") + return 1 if errors else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.codex/skills/github-pages-academic-portfolio/scripts/check_contrast.py b/.codex/skills/github-pages-academic-portfolio/scripts/check_contrast.py new file mode 100644 index 0000000..f97e77a --- /dev/null +++ b/.codex/skills/github-pages-academic-portfolio/scripts/check_contrast.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Check WCAG contrast ratios for one or more named color pairs.""" + +from __future__ import annotations + +import argparse +import re +import sys + + +HEX_COLOR = re.compile(r"^#(?P[0-9a-fA-F]{6})$") + + +def parse_color(value: str) -> tuple[int, int, int]: + match = HEX_COLOR.fullmatch(value) + if not match: + raise argparse.ArgumentTypeError(f"Expected a six-digit hex color, got {value!r}") + raw = match.group("value") + return tuple(int(raw[index : index + 2], 16) for index in (0, 2, 4)) + + +def channel_luminance(channel: int) -> float: + value = channel / 255 + return value / 12.92 if value <= 0.04045 else ((value + 0.055) / 1.055) ** 2.4 + + +def relative_luminance(color: tuple[int, int, int]) -> float: + red, green, blue = (channel_luminance(channel) for channel in color) + return 0.2126 * red + 0.7152 * green + 0.0722 * blue + + +def contrast_ratio(foreground: tuple[int, int, int], background: tuple[int, int, int]) -> float: + lighter, darker = sorted( + (relative_luminance(foreground), relative_luminance(background)), reverse=True + ) + return (lighter + 0.05) / (darker + 0.05) + + +def parse_pair(value: str) -> tuple[str, str, str, float]: + try: + name, foreground, background, minimum = value.split(":", maxsplit=3) + parse_color(foreground) + parse_color(background) + threshold = float(minimum) + except (ValueError, argparse.ArgumentTypeError) as error: + raise argparse.ArgumentTypeError( + "Pair must be NAME:#RRGGBB:#RRGGBB:MINIMUM, for example body:#172033:#f7f9fc:4.5" + ) from error + if not name.strip() or threshold <= 0: + raise argparse.ArgumentTypeError("Pair name must be non-empty and minimum must be positive") + return name.strip(), foreground, background, threshold + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--pair", action="append", required=True, type=parse_pair) + args = parser.parse_args() + + failed = False + for name, foreground, background, minimum in args.pair: + ratio = contrast_ratio(parse_color(foreground), parse_color(background)) + passed = ratio >= minimum + failed |= not passed + status = "PASS" if passed else "FAIL" + print(f"{status} {name}: {ratio:.2f}:1 (minimum {minimum:.1f}:1)") + + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 75bf9e6..0184f18 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,6 +11,7 @@ on: - "**.html" - "**.js" - "**.liquid" + - "**/*.scss" - "**/*.md" - "**.yml" - "Gemfile" @@ -27,6 +28,7 @@ on: - "**.html" - "**.js" - "**.liquid" + - "**/*.scss" - "**/*.md" - "**.yml" - "Gemfile" diff --git a/.gitignore b/.gitignore index ea7c72f..420815e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,7 @@ assets/libs/ assets/rendercv/rendercv_output/*.typ al-folio-upgrade-report.md node_modules/ +__pycache__/ +*.py[cod] tmp/ vendor diff --git a/README.md b/README.md index 50522e7..512f83a 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,14 @@ JEKYLL_ENV=production bundle exec jekyll build Portfolio claims should be supported by project records, repositories, reports, or reproducible results. Template demonstrations and unrelated sample material are intentionally excluded. +## Portfolio maintenance skill + +The repository includes a reusable Codex skill at +`.codex/skills/github-pages-academic-portfolio/`. It defines the design, accessibility, evidence, SEO, validation, and +pull-request workflow for future portfolio work. + +Invoke it with `$github-pages-academic-portfolio` when auditing, redesigning, optimizing, or extending this site. + ## License The site source is available under the terms in [LICENSE](LICENSE). diff --git a/_config.yml b/_config.yml index 3ee2f5e..7eca006 100644 --- a/_config.yml +++ b/_config.yml @@ -93,14 +93,14 @@ al_folio: # ----------------------------------------------------------------------------- navbar_fixed: true -footer_fixed: true +footer_fixed: false search_enabled: true socials_in_search: true posts_in_search: false bib_search: false # Dimensions -max_width: 930px +max_width: 1060px # ----------------------------------------------------------------------------- # Open Graph & Schema.org @@ -207,6 +207,7 @@ kramdown: # Includes & excludes include: ["_pages"] exclude: + - .codex - al-folio-upgrade-report.md - Gemfile - Gemfile.lock diff --git a/_layouts/portfolio-home.liquid b/_layouts/portfolio-home.liquid new file mode 100644 index 0000000..2591b6d --- /dev/null +++ b/_layouts/portfolio-home.liquid @@ -0,0 +1,35 @@ +--- +layout: default +--- +
+
+
+

Project Researcher · University of Turku

+

Sabuj C. Bhowmick

+

Data science, machine learning, and computational biology

+

+ I develop reproducible analytical workflows for scientific and applied problems, with an emphasis on rigorous validation, interpretable + results, and research software that others can inspect and reuse. +

+ +
+ +
+ Portrait of Sabuj C. Bhowmick +
+
+ + {{ content }} +
diff --git a/_pages/about.md b/_pages/about.md index 8161eda..ac0bbd3 100644 --- a/_pages/about.md +++ b/_pages/about.md @@ -1,30 +1,112 @@ --- -layout: about +layout: portfolio-home title: About permalink: / -subtitle: Data Science · Machine Learning · Computational Biology · Applied Statistics +description: Academic and technical portfolio of Sabuj C. Bhowmick, Project Researcher and MSc Data Analytics student at the University of Turku. nav: false -profile: - align: right - image: sabuj.jpg - image_circular: false --- -I am **Sabuj C. Bhowmick**, an MSc student in Information and Communication Technology, specializing in **Data Analytics**, at the University of Turku. My interdisciplinary background spans statistics, mathematical sciences, public health, Internet of Things engineering, machine learning, and computational research. +
+
+
+

About

+

Interdisciplinary analysis with scientific discipline.

+
+
+

+ I am a Project Researcher in the Department of Computing and an MSc Data Analytics student at the University + of Turku. My background spans statistics, mathematical sciences, public health, Internet of Things + engineering, machine learning, and computational research. +

+

+ I work on reproducible and interpretable data-driven solutions, particularly in computational biology, + microbiome analytics, biostatistics, predictive modeling, and research software. My approach emphasizes data + quality, leakage prevention, appropriate validation, transparent evaluation, and outputs that can be + reproduced by other researchers. +

+
+
-I build **reproducible and interpretable data-driven solutions**, with particular interest in computational biology, microbiome analytics, biostatistics, predictive modeling, and research software. My work emphasizes validation design, data quality, leakage prevention, transparent evaluation, and results that can be reproduced by others. + +
-## Current focus +
+
+

Technical focus

+

Methods, software, and domain knowledge working together.

+

+ My portfolio connects statistical reasoning with implementation: the evaluation design, data pipeline, and + software behavior are treated as part of the scientific result. +

+
-My current research and software work examines **Joint Robust Principal Component Analysis (Joint-RPCA)** for multi-omics integration. I am comparing R/Bioconductor and Python implementations, aligning preprocessing and evaluation, and developing reproducible validation workflows for microbiome data. +
+
+ +

Computational biology

+

Microbiome analysis, multi-omics integration, compositional data, Bioconductor, and Joint-RPCA workflows.

+
+
+ +

Machine learning and statistics

+

Predictive modeling, time-series analysis, group-aware validation, model comparison, and interpretation.

+
+
+ +

Research software

+

Python, R, SQL, Git/GitHub, automated testing, Quarto, documentation, and reproducible analytical pipelines.

+
+
+
-## Technical direction +
+
+

Research practice

+

What I optimize for

+
+
+
+ Validation integrity + Evaluation schemes should match the intended scientific or operational use. +
+
+ Reproducibility + Inputs, transformations, configurations, versions, and outputs should remain traceable. +
+
+ Interpretability + Results should communicate assumptions, uncertainty, limitations, and practical meaning. +
+
+
-- **Data science and statistics:** regression, classification, time-series analysis, experimental design, and model evaluation -- **Machine learning engineering:** reproducible pipelines, cross-validation, feature engineering, testing, and performance analysis -- **Computational biology:** Bioconductor, microbiome data, multi-omics integration, and compositional data analysis -- **Research software:** Python, R, SQL, Git/GitHub, Quarto, R Markdown, and LaTeX - -I am open to research collaboration, thesis and PhD opportunities, and data science or machine learning engineering roles where methodological rigor and practical impact are both important. - -[Explore selected projects](/projects/) · [View research work](/research/) · [Read my CV](/cv/) +
+
+

Research and technical collaboration

+

+ I welcome discussions about computational biology, data science, research software, thesis or PhD work, and + methodologically rigorous machine-learning projects. +

+
+ + Contact me + +
diff --git a/_pages/contact.md b/_pages/contact.md index bb12c6c..cebc704 100644 --- a/_pages/contact.md +++ b/_pages/contact.md @@ -9,7 +9,20 @@ nav_order: 5 I welcome enquiries about research collaboration, thesis or PhD opportunities, data science, machine learning engineering, bioinformatics, and reproducible scientific software. Please reach me through one of my public professional profiles: -- **GitHub:** [github.com/sabujcb](https://github.com/sabujcb) -- **LinkedIn:** [Sabuj Bhowmick](https://www.linkedin.com/in/sabuj-bhowmick-34920874/) + For project-specific questions, linking the relevant repository or project page will help me respond precisely. diff --git a/_pages/cv.md b/_pages/cv.md index baad467..dd69043 100644 --- a/_pages/cv.md +++ b/_pages/cv.md @@ -25,8 +25,8 @@ description: Education, research experience, technical skills, and selected proj border: 1px solid var(--cv-border); border-radius: 1rem; background: - radial-gradient(circle at top right, rgba(111, 66, 193, 0.15), transparent 42%), - linear-gradient(135deg, rgba(0, 123, 255, 0.11), rgba(32, 201, 151, 0.07)); + radial-gradient(circle at top right, var(--academic-gold-soft), transparent 42%), + linear-gradient(135deg, var(--academic-accent-soft), var(--academic-teal-soft)); } .cv-eyebrow { diff --git a/_pages/projects.md b/_pages/projects.md index dfd1e0b..846ff47 100644 --- a/_pages/projects.md +++ b/_pages/projects.md @@ -22,8 +22,8 @@ nav_order: 3 border: 1px solid var(--project-border); border-radius: calc(var(--project-radius) + 0.25rem); background: - radial-gradient(circle at top right, rgba(111, 66, 193, 0.16), transparent 40%), - linear-gradient(135deg, rgba(0, 123, 255, 0.12), rgba(32, 201, 151, 0.08)); + radial-gradient(circle at top right, var(--academic-gold-soft), transparent 40%), + linear-gradient(135deg, var(--academic-accent-soft), var(--academic-teal-soft)); } .projects-eyebrow { @@ -148,7 +148,7 @@ nav_order: 3 border: 1px solid var(--project-border); border-radius: var(--project-radius); background: var(--project-surface); - box-shadow: 0 0.35rem 1.25rem rgba(0, 0, 0, 0.045); + box-shadow: var(--academic-shadow); transition: transform 160ms ease, border-color 160ms ease, @@ -158,7 +158,7 @@ nav_order: 3 .project-card:hover { transform: translateY(-3px); border-color: var(--global-theme-color); - box-shadow: 0 0.75rem 1.75rem rgba(0, 0, 0, 0.08); + box-shadow: var(--academic-shadow-strong); } .project-card-topline { @@ -215,7 +215,7 @@ nav_order: 3 padding: 0.85rem; border-left: 3px solid var(--global-theme-color); border-radius: 0.65rem; - background: rgba(0, 123, 255, 0.065); + background: var(--academic-accent-soft); } .project-metric strong { @@ -302,7 +302,7 @@ nav_order: 3 .project-principle { padding: 1rem; border-radius: 0.8rem; - background: rgba(0, 123, 255, 0.055); + background: var(--academic-accent-soft); } .project-principle h3 { diff --git a/_pages/research.md b/_pages/research.md index 8d2bed0..7a84507 100644 --- a/_pages/research.md +++ b/_pages/research.md @@ -9,10 +9,16 @@ nav_order: 2 ## Current research direction -My current work focuses on **reproducible multi-omics integration** for microbiome research. The central methodological problem is ensuring that Joint-RPCA workflows implemented in different software ecosystems are compared using equivalent inputs, preprocessing rules, model settings, and validation criteria. +
+ +As a **Project Researcher** in the University of Turku Department of Computing, I support data analytics for the NoveLyme project. My recent research-software work also focuses on **reproducible multi-omics integration** for microbiome research. + +
### Joint-RPCA and microbiome analytics +The central methodological problem is ensuring that Joint-RPCA workflows implemented in different software ecosystems are compared using equivalent inputs, preprocessing rules, model settings, and validation criteria. + The work includes: - comparing R/Bioconductor and Python/Gemelli implementations; diff --git a/_sass/_academic.scss b/_sass/_academic.scss new file mode 100644 index 0000000..e78a40b --- /dev/null +++ b/_sass/_academic.scss @@ -0,0 +1,736 @@ +/******************************************************************************* + * Site-owned academic portfolio design system. + ******************************************************************************/ + +:root { + --global-bg-color: #f7f9fc; + --global-code-bg-color: #e9eff6; + --global-text-color: #172033; + --global-text-color-light: #536277; + --global-theme-color: #0b5cad; + --global-hover-color: #084984; + --global-hover-text-color: #ffffff; + --global-footer-bg-color: #0b1f3a; + --global-footer-text-color: #dce6f2; + --global-footer-link-color: #ffffff; + --global-divider-color: #d7e0ea; + --global-card-bg-color: #ffffff; + --global-highlight-color: #8a5a00; + + --academic-heading: #0b1f3a; + --academic-teal: #0f766e; + --academic-gold: #d6a43b; + --academic-focus: #b66a00; + --academic-surface-soft: #eef4f8; + --academic-surface-muted: #e4edf5; + --academic-border-strong: #b8c7d6; + --academic-accent-soft: rgba(11, 92, 173, 0.09); + --academic-teal-soft: rgba(15, 118, 110, 0.1); + --academic-gold-soft: rgba(214, 164, 59, 0.15); + --academic-nav-bg: rgba(247, 249, 252, 0.94); + --academic-hero-gradient: linear-gradient(135deg, #edf4fa 0%, #f6faf9 58%, #fbf7ed 100%); + --academic-shadow: 0 0.8rem 2.2rem rgba(11, 31, 58, 0.07); + --academic-shadow-strong: 0 1.25rem 3rem rgba(11, 31, 58, 0.12); + --academic-radius-sm: 0.7rem; + --academic-radius: 1rem; + --academic-radius-lg: 1.5rem; + --academic-reading-width: 72ch; +} + +html[data-theme="dark"] { + --global-bg-color: #0b1220; + --global-code-bg-color: #18263a; + --global-text-color: #e7edf5; + --global-text-color-light: #a9b7c8; + --global-theme-color: #72c7e8; + --global-hover-color: #a6def2; + --global-hover-text-color: #08111f; + --global-footer-bg-color: #070d18; + --global-footer-text-color: #c8d4e2; + --global-footer-link-color: #ffffff; + --global-divider-color: #2b3a4f; + --global-card-bg-color: #111c2e; + --global-highlight-color: #e8bd62; + + --academic-heading: #f0f5fa; + --academic-teal: #67d7c2; + --academic-gold: #e8bd62; + --academic-focus: #ffd166; + --academic-surface-soft: #15243a; + --academic-surface-muted: #1b2d45; + --academic-border-strong: #41536a; + --academic-accent-soft: rgba(114, 199, 232, 0.12); + --academic-teal-soft: rgba(103, 215, 194, 0.11); + --academic-gold-soft: rgba(232, 189, 98, 0.12); + --academic-nav-bg: rgba(11, 18, 32, 0.94); + --academic-hero-gradient: linear-gradient(135deg, #122139 0%, #11262d 58%, #2a2419 100%); + --academic-shadow: 0 0.8rem 2.2rem rgba(0, 0, 0, 0.22); + --academic-shadow-strong: 0 1.25rem 3rem rgba(0, 0, 0, 0.32); +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: + "Roboto", + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-size: 1rem; + line-height: 1.65; + text-rendering: optimizeLegibility; + background: radial-gradient(circle at 92% 4%, var(--academic-accent-soft), transparent 22rem), var(--global-bg-color); +} + +body.fixed-top-nav { + padding-top: 64px; +} + +h1, +h2, +h3, +h4, +h5, +h6, +.navbar-brand { + color: var(--academic-heading); + font-family: "Roboto Slab", Georgia, serif; + letter-spacing: -0.015em; +} + +p, +li { + text-wrap: pretty; +} + +a, +button, +input, +select, +textarea { + &:focus-visible { + outline: 3px solid var(--academic-focus); + outline-offset: 3px; + border-radius: 0.25rem; + } +} + +::selection { + color: var(--global-text-color); + background: var(--academic-gold-soft); +} + +.container[role="main"] { + padding-inline: clamp(1rem, 3vw, 1.8rem); +} + +.navbar { + min-height: 64px; + border-bottom-color: var(--global-divider-color); + background: var(--academic-nav-bg); + box-shadow: 0 0.35rem 1.25rem rgba(11, 31, 58, 0.06); + opacity: 1; + backdrop-filter: blur(14px); + + .navbar-brand { + color: var(--academic-heading); + font-weight: 700; + letter-spacing: -0.02em; + } + + .navbar-nav .nav-item .nav-link, + #search-toggle .nav-link { + display: inline-flex; + min-height: 44px; + align-items: center; + padding: 0.55rem 0.68rem; + border-radius: 0.55rem; + font-size: 0.93rem; + font-weight: 500; + text-decoration: none; + } + + .navbar-nav .nav-item .nav-link:hover, + #search-toggle .nav-link:hover { + background: var(--academic-accent-soft); + } + + &.navbar-light .navbar-nav .nav-item.active > .nav-link { + background: var(--academic-accent-soft); + color: var(--global-theme-color); + font-weight: 700; + } +} + +#light-toggle, +#search-toggle { + min-width: 44px; + min-height: 44px; +} + +.post-header { + position: relative; + max-width: var(--academic-reading-width); + margin-bottom: 2.2rem; + padding-bottom: 1.25rem; + border-bottom: 1px solid var(--global-divider-color); + + &::after { + position: absolute; + bottom: -1px; + left: 0; + width: 4.5rem; + height: 3px; + border-radius: 999px; + background: linear-gradient(90deg, var(--global-theme-color), var(--academic-teal)); + content: ""; + } + + .post-title { + margin-bottom: 0.45rem; + font-size: clamp(2rem, 5vw, 3.2rem); + font-weight: 700; + line-height: 1.08; + } + + .post-description, + .desc { + max-width: 66ch; + margin: 0; + color: var(--global-text-color-light); + font-size: 1.04rem; + line-height: 1.65; + } +} + +.post article > p, +.post article > ul, +.post article > ol { + max-width: var(--academic-reading-width); +} + +.post article > h2 { + margin-top: 2.8rem; + margin-bottom: 0.9rem; + font-size: clamp(1.45rem, 3vw, 1.9rem); +} + +.post article > h3 { + margin-top: 1.8rem; + font-size: 1.18rem; +} + +.academic-home { + display: grid; + gap: clamp(3.2rem, 8vw, 5.6rem); + padding-bottom: 2rem; +} + +.academic-hero { + position: relative; + isolation: isolate; + display: grid; + grid-template-columns: minmax(0, 1.45fr) minmax(15rem, 0.72fr); + gap: clamp(2rem, 5vw, 4.5rem); + align-items: center; + overflow: hidden; + padding: clamp(1.4rem, 4vw, 3.4rem); + border: 1px solid var(--global-divider-color); + border-radius: var(--academic-radius-lg); + background: var(--academic-hero-gradient); + box-shadow: var(--academic-shadow); + + &::before, + &::after { + position: absolute; + z-index: -1; + border-radius: 999px; + content: ""; + } + + &::before { + top: -8rem; + right: -5rem; + width: 19rem; + height: 19rem; + border: 1px solid var(--academic-border-strong); + opacity: 0.34; + } + + &::after { + right: 22%; + bottom: -7rem; + width: 12rem; + height: 12rem; + background: var(--academic-gold-soft); + } +} + +.academic-eyebrow { + margin: 0 0 0.8rem; + color: var(--academic-teal); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.academic-hero h1 { + max-width: 12ch; + margin: 0; + font-size: clamp(2.55rem, 7vw, 4.65rem); + font-weight: 700; + line-height: 0.98; + text-wrap: balance; +} + +.academic-hero-title { + max-width: 40rem; + margin: 1rem 0 0; + color: var(--global-theme-color); + font-size: clamp(1.05rem, 2.4vw, 1.32rem); + font-weight: 700; + line-height: 1.45; +} + +.academic-hero-thesis { + max-width: 62ch; + margin: 1.2rem 0 0; + color: var(--global-text-color-light); + font-size: 1.02rem; + line-height: 1.72; +} + +.academic-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 1.55rem; +} + +.academic-button { + display: inline-flex; + min-height: 46px; + align-items: center; + justify-content: center; + gap: 0.45rem; + padding: 0.68rem 1rem; + border: 1px solid var(--academic-border-strong); + border-radius: 0.72rem; + background: var(--global-card-bg-color); + color: var(--global-text-color); + font-size: 0.9rem; + font-weight: 700; + text-decoration: none; + transition: + transform 160ms ease, + border-color 160ms ease, + box-shadow 160ms ease; + + &:hover { + transform: translateY(-2px); + border-color: var(--global-theme-color); + color: var(--global-theme-color); + text-decoration: none; + box-shadow: var(--academic-shadow); + } +} + +.academic-button-primary { + border-color: var(--global-theme-color); + background: var(--global-theme-color); + color: #ffffff; + + &:hover { + color: #ffffff; + filter: brightness(0.94); + } +} + +html[data-theme="dark"] .academic-button-primary { + color: #08111f; + + &:hover { + color: #08111f; + } +} + +html[data-theme="dark"] .project-action-primary, +html[data-theme="dark"] .cv-action-primary { + color: #08111f; + + &:hover { + color: #08111f; + } +} + +.academic-portrait { + position: relative; + justify-self: end; + width: min(100%, 19rem); + padding: 0.55rem; + border: 1px solid var(--academic-border-strong); + border-radius: 1.5rem 1.5rem 0.75rem 1.5rem; + background: var(--global-card-bg-color); + box-shadow: var(--academic-shadow-strong); + + &::after { + position: absolute; + right: -0.65rem; + bottom: -0.65rem; + z-index: -1; + width: 72%; + height: 72%; + border-radius: 1rem; + background: var(--academic-teal); + content: ""; + opacity: 0.16; + } + + img { + display: block; + width: 100%; + aspect-ratio: 4 / 5; + border-radius: 1.1rem 1.1rem 0.5rem 1.1rem; + object-fit: cover; + object-position: center 24%; + } +} + +.academic-overview { + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(16rem, 0.75fr); + gap: clamp(1.2rem, 4vw, 2.5rem); + align-items: start; +} + +.academic-section-heading { + max-width: 50rem; + margin-bottom: 1.55rem; + + h2 { + margin: 0 0 0.55rem; + font-size: clamp(1.7rem, 4vw, 2.35rem); + line-height: 1.15; + } + + p { + max-width: 66ch; + margin: 0; + color: var(--global-text-color-light); + line-height: 1.7; + } +} + +.academic-about-copy { + max-width: var(--academic-reading-width); + font-size: 1.02rem; + line-height: 1.78; + + p:last-child { + margin-bottom: 0; + } +} + +.academic-current, +.academic-card, +.academic-principle, +.academic-contact-card { + border: 1px solid var(--global-divider-color); + background: var(--global-card-bg-color); + box-shadow: var(--academic-shadow); +} + +.academic-current { + padding: 1.35rem; + border-radius: var(--academic-radius); + border-top: 4px solid var(--academic-teal); + + h2 { + margin: 0 0 0.7rem; + font-size: 1.2rem; + } + + p { + margin: 0; + color: var(--global-text-color-light); + font-size: 0.92rem; + line-height: 1.65; + } + + dl { + display: grid; + gap: 0.7rem; + margin: 1.1rem 0 0; + } + + dt { + color: var(--global-text-color-light); + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + } + + dd { + margin: 0.15rem 0 0; + font-size: 0.9rem; + font-weight: 600; + } +} + +.academic-card-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; +} + +.academic-card { + padding: 1.35rem; + border-radius: var(--academic-radius); + + .academic-card-number { + display: inline-flex; + width: 2.25rem; + height: 2.25rem; + align-items: center; + justify-content: center; + margin-bottom: 1rem; + border-radius: 0.65rem; + background: var(--academic-accent-soft); + color: var(--global-theme-color); + font-family: "Roboto Slab", Georgia, serif; + font-weight: 700; + } + + &:nth-child(2) .academic-card-number { + background: var(--academic-teal-soft); + color: var(--academic-teal); + } + + &:nth-child(3) .academic-card-number { + background: var(--academic-gold-soft); + color: var(--academic-heading); + } + + h3 { + margin: 0 0 0.55rem; + font-size: 1.08rem; + } + + p { + margin: 0; + color: var(--global-text-color-light); + font-size: 0.92rem; + line-height: 1.65; + } +} + +.academic-principles { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.85rem; +} + +.academic-principle { + padding: 1.05rem 1.15rem; + border-radius: 0.85rem; + + strong { + display: block; + margin-bottom: 0.25rem; + color: var(--academic-heading); + font-family: "Roboto Slab", Georgia, serif; + } + + span { + color: var(--global-text-color-light); + font-size: 0.86rem; + line-height: 1.5; + } +} + +.academic-closing { + display: flex; + gap: 1.5rem; + align-items: center; + justify-content: space-between; + padding: clamp(1.25rem, 4vw, 2rem); + border: 1px solid var(--global-divider-color); + border-radius: var(--academic-radius); + background: var(--academic-surface-soft); + + h2 { + margin: 0 0 0.35rem; + font-size: clamp(1.35rem, 3vw, 1.75rem); + } + + p { + max-width: 58ch; + margin: 0; + color: var(--global-text-color-light); + } +} + +.academic-lead { + max-width: var(--academic-reading-width); + padding: 1.25rem 1.35rem; + border-left: 4px solid var(--academic-teal); + border-radius: 0 var(--academic-radius-sm) var(--academic-radius-sm) 0; + background: var(--academic-teal-soft); + + p:last-child { + margin-bottom: 0; + } +} + +.academic-contact-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin: 1.5rem 0 2rem; +} + +.academic-contact-card { + display: block; + padding: 1.25rem; + border-radius: var(--academic-radius); + color: var(--global-text-color); + text-decoration: none; + transition: + transform 160ms ease, + border-color 160ms ease, + box-shadow 160ms ease; + + &:hover { + transform: translateY(-2px); + border-color: var(--global-theme-color); + color: var(--global-text-color); + text-decoration: none; + box-shadow: var(--academic-shadow-strong); + } + + strong, + span { + display: block; + } + + strong { + margin-bottom: 0.35rem; + color: var(--academic-heading); + font-family: "Roboto Slab", Georgia, serif; + font-size: 1.05rem; + } + + span { + color: var(--global-text-color-light); + font-size: 0.88rem; + } +} + +.portfolio-projects, +.professional-cv { + --project-radius: var(--academic-radius); + --project-border: var(--global-divider-color); + --project-surface: var(--global-card-bg-color); + --cv-border: var(--global-divider-color); + --cv-surface: var(--global-card-bg-color); +} + +.project-card, +.cv-entry, +.cv-skill, +.cv-project, +.cv-language, +.cv-interest { + box-shadow: var(--academic-shadow); +} + +.project-card:hover { + box-shadow: var(--academic-shadow-strong); +} + +footer { + border-top: 1px solid var(--global-divider-color); + font-size: 0.82rem; + line-height: 1.55; +} + +@media (max-width: 767px) { + body.fixed-top-nav { + padding-top: 58px; + } + + .academic-hero, + .academic-overview, + .academic-card-grid, + .academic-principles, + .academic-contact-grid { + grid-template-columns: 1fr; + } + + .academic-hero { + gap: 2rem; + } + + .academic-portrait { + justify-self: start; + width: min(75vw, 17rem); + } + + .academic-closing { + align-items: flex-start; + flex-direction: column; + } + + .navbar .navbar-collapse-main { + padding: 0.65rem 0 0.85rem; + } + + .navbar .navbar-nav .nav-item, + .navbar .navbar-nav .nav-item .nav-link, + .navbar #search-toggle { + width: 100%; + } +} + +@media (max-width: 420px) { + .academic-hero { + padding: 1.2rem; + border-radius: 1.15rem; + } + + .academic-hero h1 { + font-size: clamp(2.3rem, 15vw, 3.15rem); + } + + .academic-actions, + .academic-button { + width: 100%; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } + + .academic-button:hover, + .academic-contact-card:hover, + .project-card:hover { + transform: none; + } +} diff --git a/assets/css/main.scss b/assets/css/main.scss new file mode 100644 index 0000000..092c4a4 --- /dev/null +++ b/assets/css/main.scss @@ -0,0 +1,26 @@ +--- +--- +@charset "utf-8"; + +@use "sass:math"; +@use "sass:string"; + +@use "variables" with ( + $max-content-width: {{ site.max_width | default: "930px" }} +); + +@use "themes"; +@use "layout"; +@use "typography"; +@use "navbar"; +@use "footer"; +@use "blog"; +@use "publications"; +@use "components"; +@use "utilities"; +@use "tabs"; +@use "teachings"; +@use "typograms"; + +// Site-owned academic portfolio design layer. +@use "academic";