diff --git a/README.md b/README.md index e2e8357..d155c87 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ Refresh full articles with `node scripts/import-articles.mjs`. Its sanitizer pre Project images live in assets/work. Personal and project image placeholders are labeled SVGs generated by the build; update the project data or About photo strip when replacement images are available. Enterprise summaries deliberately exclude source code, architecture and client-sensitive details. The supplied resume is preserved byte for byte at both existing PDF paths. +Markdown Viewer has a dedicated case study in `scripts/markdown-case-study.mjs`, styled by `work-case.css`. It uses eight author-supplied screenshots, seven anchored sections, and a progressively enhanced screenshot dialog in `work-case.js`. Shared project technology labels and icons are rendered by `scripts/technology-icons.mjs`. See the [Markdown Viewer source audit](docs/markdown-viewer-case-study.md) before updating product claims, screenshots, or technology assets. + ## Verify ```sh diff --git a/assets/README.md b/assets/README.md index 1c7cb91..775f88e 100644 --- a/assets/README.md +++ b/assets/README.md @@ -1,5 +1,7 @@ # Published assets +The Markdown Viewer project page uses eight author-supplied September 2026 screenshots, encoded proportionally as WebP under `work/markdown-viewer/`. Full provenance, selection decisions and source filenames are in the [case-study source audit](../docs/markdown-viewer-case-study.md). Technology marks are local SVGs from Devicon and Simple Icons; action and API symbols come from Lucide. See [sources and licenses](tools/SOURCES.md). + Article cards and reader headers now use original inline SVG compositions from scripts/editorial.mjs, styled by editorial.css. The document, Git, AI, vision, audio and web illustrations are code-created for this portfolio. Historical imported covers remain in the source snapshot for provenance but are not displayed in the editorial layout. Project screenshots depict Baivab’s own projects. WebP variants resize and compress the original captures. Clearly labeled SVG placeholders in `assets/placeholders/` and three personal photo placeholders in `assets/about/` can be replaced with the author’s images. The fonts were obtained from official, licensed Google Fonts sources. diff --git a/assets/tools/LICENSE-SIMPLE-ICONS.txt b/assets/tools/LICENSE-SIMPLE-ICONS.txt new file mode 100644 index 0000000..70d4a7b --- /dev/null +++ b/assets/tools/LICENSE-SIMPLE-ICONS.txt @@ -0,0 +1,30 @@ +# CC0 1.0 Universal + +## Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an “owner”) of an original work of authorship and/or a database (each, a “Work”). + +Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works (“Commons”) that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the “Affirmer”), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights (“Copyright and Related Rights”). Copyright and Related Rights include, but are not limited to, the following: + 1. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + 2. moral rights retained by the original author(s) and/or performer(s); + 3. publicity and privacy rights pertaining to a person’s image or likeness depicted in a Work; + 4. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(i), below; + 5. rights protecting the extraction, dissemination, use and reuse of data in a Work; + 6. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + 7. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the “Waiver”). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer’s heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer’s express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer’s express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer’s Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the “License”). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer’s express Statement of Purpose. + +4. Limitations and Disclaimers. + 1. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + 2. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + 3. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person’s Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + 4. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. + +For more information, please see . diff --git a/assets/tools/SOURCES.md b/assets/tools/SOURCES.md index e2fd848..c114e19 100644 --- a/assets/tools/SOURCES.md +++ b/assets/tools/SOURCES.md @@ -14,3 +14,11 @@ The original, full-color brand marks are locally served from [Devicon](https://g - [cloudflare.svg](https://github.com/devicons/devicon/blob/7330accdbc47e2dc0c19789a48533c4a3c50fe58/icons/cloudflare/cloudflare-original.svg) - [vercel.svg](https://github.com/devicons/devicon/blob/7330accdbc47e2dc0c19789a48533c4a3c50fe58/icons/vercel/vercel-original.svg) - [figma.svg](https://github.com/devicons/devicon/blob/7330accdbc47e2dc0c19789a48533c4a3c50fe58/icons/figma/figma-original.svg) + +## Project technology labels — 14 September 2026 + +Additional Devicon assets use the same pinned commit and MIT license: `bootstrap`, `css3`, `html5`, `mysql`, `playwright`, `postgresql`, `redis`, `scikitlearn`, `solidity`, `spring`, and `streamlit`. Original paths follow `icons//-original.svg` in that repository. + +The `django`, `ethereum`, `langchain`, `mediapipe`, `meta` (Llama 2's developer), `neutralinojs`, and `web3dotjs` marks come from [Simple Icons](https://github.com/simple-icons/simple-icons/tree/4ba19240849175ab4b855a732ab98c0f87cfb714/icons), pinned to `4ba19240849175ab4b855a732ab98c0f87cfb714`. Source paths are `icons/.svg`; the CC0 license is saved as `LICENSE-SIMPLE-ICONS.txt`. Brand names and marks remain their owners' property. + +The unmodified SVG symbols in `lucide/` come from [Lucide 0.468.0](https://github.com/lucide-icons/lucide/tree/f12b0de177fbc2a6795e99be065887e72b237123/icons), with the upstream ISC/Feather MIT notice in `lucide/LICENSE.txt`. These identify actions and concepts such as IndexedDB and browser APIs; semantic symbols are not presented as brand logos. Every technology icon has an adjacent visible label. diff --git a/assets/tools/bootstrap.svg b/assets/tools/bootstrap.svg new file mode 100644 index 0000000..00af29d --- /dev/null +++ b/assets/tools/bootstrap.svg @@ -0,0 +1 @@ + diff --git a/assets/tools/css3.svg b/assets/tools/css3.svg new file mode 100644 index 0000000..ff5b6ce --- /dev/null +++ b/assets/tools/css3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tools/django.svg b/assets/tools/django.svg new file mode 100644 index 0000000..7bb3b99 --- /dev/null +++ b/assets/tools/django.svg @@ -0,0 +1 @@ +Django \ No newline at end of file diff --git a/assets/tools/ethereum.svg b/assets/tools/ethereum.svg new file mode 100644 index 0000000..fc2c96d --- /dev/null +++ b/assets/tools/ethereum.svg @@ -0,0 +1 @@ +Ethereum \ No newline at end of file diff --git a/assets/tools/html5.svg b/assets/tools/html5.svg new file mode 100644 index 0000000..c2dda3a --- /dev/null +++ b/assets/tools/html5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tools/langchain.svg b/assets/tools/langchain.svg new file mode 100644 index 0000000..3edb272 --- /dev/null +++ b/assets/tools/langchain.svg @@ -0,0 +1 @@ +LangChain \ No newline at end of file diff --git a/assets/tools/lucide/LICENSE.txt b/assets/tools/lucide/LICENSE.txt new file mode 100644 index 0000000..325e8ff --- /dev/null +++ b/assets/tools/lucide/LICENSE.txt @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/assets/tools/lucide/chart-no-axes-combined.svg b/assets/tools/lucide/chart-no-axes-combined.svg new file mode 100644 index 0000000..be0ce52 --- /dev/null +++ b/assets/tools/lucide/chart-no-axes-combined.svg @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/assets/tools/lucide/code-xml.svg b/assets/tools/lucide/code-xml.svg new file mode 100644 index 0000000..74f2299 --- /dev/null +++ b/assets/tools/lucide/code-xml.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/cpu.svg b/assets/tools/lucide/cpu.svg new file mode 100644 index 0000000..3fa43e8 --- /dev/null +++ b/assets/tools/lucide/cpu.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/assets/tools/lucide/database.svg b/assets/tools/lucide/database.svg new file mode 100644 index 0000000..f95db0e --- /dev/null +++ b/assets/tools/lucide/database.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/file-code-2.svg b/assets/tools/lucide/file-code-2.svg new file mode 100644 index 0000000..4e0634a --- /dev/null +++ b/assets/tools/lucide/file-code-2.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/assets/tools/lucide/folder-open.svg b/assets/tools/lucide/folder-open.svg new file mode 100644 index 0000000..b01f60f --- /dev/null +++ b/assets/tools/lucide/folder-open.svg @@ -0,0 +1,13 @@ + + + diff --git a/assets/tools/lucide/github.svg b/assets/tools/lucide/github.svg new file mode 100644 index 0000000..60c011f --- /dev/null +++ b/assets/tools/lucide/github.svg @@ -0,0 +1,14 @@ + + + + diff --git a/assets/tools/lucide/globe.svg b/assets/tools/lucide/globe.svg new file mode 100644 index 0000000..53c80ee --- /dev/null +++ b/assets/tools/lucide/globe.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/history.svg b/assets/tools/lucide/history.svg new file mode 100644 index 0000000..67152e4 --- /dev/null +++ b/assets/tools/lucide/history.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/link.svg b/assets/tools/lucide/link.svg new file mode 100644 index 0000000..645e746 --- /dev/null +++ b/assets/tools/lucide/link.svg @@ -0,0 +1,14 @@ + + + + diff --git a/assets/tools/lucide/lock-keyhole.svg b/assets/tools/lucide/lock-keyhole.svg new file mode 100644 index 0000000..97fcbd1 --- /dev/null +++ b/assets/tools/lucide/lock-keyhole.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/maximize-2.svg b/assets/tools/lucide/maximize-2.svg new file mode 100644 index 0000000..852bddd --- /dev/null +++ b/assets/tools/lucide/maximize-2.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/assets/tools/lucide/messages-square.svg b/assets/tools/lucide/messages-square.svg new file mode 100644 index 0000000..5caca4a --- /dev/null +++ b/assets/tools/lucide/messages-square.svg @@ -0,0 +1,14 @@ + + + + diff --git a/assets/tools/lucide/paintbrush.svg b/assets/tools/lucide/paintbrush.svg new file mode 100644 index 0000000..1fd513e --- /dev/null +++ b/assets/tools/lucide/paintbrush.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/panels-top-left.svg b/assets/tools/lucide/panels-top-left.svg new file mode 100644 index 0000000..574e4de --- /dev/null +++ b/assets/tools/lucide/panels-top-left.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/shield-check.svg b/assets/tools/lucide/shield-check.svg new file mode 100644 index 0000000..da48f66 --- /dev/null +++ b/assets/tools/lucide/shield-check.svg @@ -0,0 +1,14 @@ + + + + diff --git a/assets/tools/lucide/users.svg b/assets/tools/lucide/users.svg new file mode 100644 index 0000000..1dcebab --- /dev/null +++ b/assets/tools/lucide/users.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/assets/tools/lucide/wallet.svg b/assets/tools/lucide/wallet.svg new file mode 100644 index 0000000..9d63559 --- /dev/null +++ b/assets/tools/lucide/wallet.svg @@ -0,0 +1,14 @@ + + + + diff --git a/assets/tools/lucide/workflow.svg b/assets/tools/lucide/workflow.svg new file mode 100644 index 0000000..20784a2 --- /dev/null +++ b/assets/tools/lucide/workflow.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/tools/lucide/x.svg b/assets/tools/lucide/x.svg new file mode 100644 index 0000000..e317943 --- /dev/null +++ b/assets/tools/lucide/x.svg @@ -0,0 +1,14 @@ + + + + diff --git a/assets/tools/mediapipe.svg b/assets/tools/mediapipe.svg new file mode 100644 index 0000000..67b318c --- /dev/null +++ b/assets/tools/mediapipe.svg @@ -0,0 +1 @@ +MediaPipe \ No newline at end of file diff --git a/assets/tools/meta.svg b/assets/tools/meta.svg new file mode 100644 index 0000000..d9a1631 --- /dev/null +++ b/assets/tools/meta.svg @@ -0,0 +1 @@ +Meta \ No newline at end of file diff --git a/assets/tools/mysql.svg b/assets/tools/mysql.svg new file mode 100644 index 0000000..207170c --- /dev/null +++ b/assets/tools/mysql.svg @@ -0,0 +1 @@ + diff --git a/assets/tools/neutralinojs.svg b/assets/tools/neutralinojs.svg new file mode 100644 index 0000000..3b0c52e --- /dev/null +++ b/assets/tools/neutralinojs.svg @@ -0,0 +1 @@ +Neutralinojs \ No newline at end of file diff --git a/assets/tools/playwright.svg b/assets/tools/playwright.svg new file mode 100644 index 0000000..3065efc --- /dev/null +++ b/assets/tools/playwright.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tools/postgresql.svg b/assets/tools/postgresql.svg new file mode 100644 index 0000000..4656cb7 --- /dev/null +++ b/assets/tools/postgresql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tools/redis.svg b/assets/tools/redis.svg new file mode 100644 index 0000000..7aabb0d --- /dev/null +++ b/assets/tools/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tools/scikitlearn.svg b/assets/tools/scikitlearn.svg new file mode 100644 index 0000000..efea74d --- /dev/null +++ b/assets/tools/scikitlearn.svg @@ -0,0 +1 @@ + diff --git a/assets/tools/solidity.svg b/assets/tools/solidity.svg new file mode 100644 index 0000000..9bcc1c3 --- /dev/null +++ b/assets/tools/solidity.svg @@ -0,0 +1 @@ + diff --git a/assets/tools/spring.svg b/assets/tools/spring.svg new file mode 100644 index 0000000..9a10e7f --- /dev/null +++ b/assets/tools/spring.svg @@ -0,0 +1 @@ + diff --git a/assets/tools/streamlit.svg b/assets/tools/streamlit.svg new file mode 100644 index 0000000..9d85b48 --- /dev/null +++ b/assets/tools/streamlit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tools/web3dotjs.svg b/assets/tools/web3dotjs.svg new file mode 100644 index 0000000..b617387 --- /dev/null +++ b/assets/tools/web3dotjs.svg @@ -0,0 +1 @@ +Web3.js \ No newline at end of file diff --git a/assets/work/markdown-viewer/d2-800.webp b/assets/work/markdown-viewer/d2-800.webp new file mode 100644 index 0000000..b04a33d Binary files /dev/null and b/assets/work/markdown-viewer/d2-800.webp differ diff --git a/assets/work/markdown-viewer/d2.webp b/assets/work/markdown-viewer/d2.webp new file mode 100644 index 0000000..825cad6 Binary files /dev/null and b/assets/work/markdown-viewer/d2.webp differ diff --git a/assets/work/markdown-viewer/logo.webp b/assets/work/markdown-viewer/logo.webp new file mode 100644 index 0000000..3147808 Binary files /dev/null and b/assets/work/markdown-viewer/logo.webp differ diff --git a/assets/work/markdown-viewer/math-800.webp b/assets/work/markdown-viewer/math-800.webp new file mode 100644 index 0000000..a7b0946 Binary files /dev/null and b/assets/work/markdown-viewer/math-800.webp differ diff --git a/assets/work/markdown-viewer/math.webp b/assets/work/markdown-viewer/math.webp new file mode 100644 index 0000000..ced6d1c Binary files /dev/null and b/assets/work/markdown-viewer/math.webp differ diff --git a/assets/work/markdown-viewer/mermaid-800.webp b/assets/work/markdown-viewer/mermaid-800.webp new file mode 100644 index 0000000..127e79c Binary files /dev/null and b/assets/work/markdown-viewer/mermaid-800.webp differ diff --git a/assets/work/markdown-viewer/mermaid.webp b/assets/work/markdown-viewer/mermaid.webp new file mode 100644 index 0000000..261f84e Binary files /dev/null and b/assets/work/markdown-viewer/mermaid.webp differ diff --git a/assets/work/markdown-viewer/mindmap-800.webp b/assets/work/markdown-viewer/mindmap-800.webp new file mode 100644 index 0000000..9c66cac Binary files /dev/null and b/assets/work/markdown-viewer/mindmap-800.webp differ diff --git a/assets/work/markdown-viewer/mindmap.webp b/assets/work/markdown-viewer/mindmap.webp new file mode 100644 index 0000000..d722a60 Binary files /dev/null and b/assets/work/markdown-viewer/mindmap.webp differ diff --git a/assets/work/markdown-viewer/model-800.webp b/assets/work/markdown-viewer/model-800.webp new file mode 100644 index 0000000..fa430eb Binary files /dev/null and b/assets/work/markdown-viewer/model-800.webp differ diff --git a/assets/work/markdown-viewer/model.webp b/assets/work/markdown-viewer/model.webp new file mode 100644 index 0000000..b29aeb2 Binary files /dev/null and b/assets/work/markdown-viewer/model.webp differ diff --git a/assets/work/markdown-viewer/music-800.webp b/assets/work/markdown-viewer/music-800.webp new file mode 100644 index 0000000..69b315e Binary files /dev/null and b/assets/work/markdown-viewer/music-800.webp differ diff --git a/assets/work/markdown-viewer/music.webp b/assets/work/markdown-viewer/music.webp new file mode 100644 index 0000000..36a368b Binary files /dev/null and b/assets/work/markdown-viewer/music.webp differ diff --git a/assets/work/markdown-viewer/timing-800.webp b/assets/work/markdown-viewer/timing-800.webp new file mode 100644 index 0000000..7b2df2c Binary files /dev/null and b/assets/work/markdown-viewer/timing-800.webp differ diff --git a/assets/work/markdown-viewer/timing.webp b/assets/work/markdown-viewer/timing.webp new file mode 100644 index 0000000..34733b6 Binary files /dev/null and b/assets/work/markdown-viewer/timing.webp differ diff --git a/assets/work/markdown-viewer/workspace-800.webp b/assets/work/markdown-viewer/workspace-800.webp new file mode 100644 index 0000000..c42b983 Binary files /dev/null and b/assets/work/markdown-viewer/workspace-800.webp differ diff --git a/assets/work/markdown-viewer/workspace.webp b/assets/work/markdown-viewer/workspace.webp new file mode 100644 index 0000000..4d2df81 Binary files /dev/null and b/assets/work/markdown-viewer/workspace.webp differ diff --git a/docs/content-sources.md b/docs/content-sources.md index f220308..dc5b1a2 100644 --- a/docs/content-sources.md +++ b/docs/content-sources.md @@ -1,5 +1,7 @@ # Content sources and editorial audit +Markdown Viewer's full project page was rebuilt after the 14 September 2026 repository documentation review. The [dedicated source audit](markdown-viewer-case-study.md) records the reviewed commit, documentation inventory, current behavior, editorial choices, and eight supplied screenshots. Current descriptions distinguish browser IndexedDB, the desktop file vault, snapshot retention, and Live Share capability metadata. + On 9 September 2026, the eleven local article titles, summaries and opening paragraphs were edited for a professional publication style. Promotional calls to comment/share and decorative emoji were removed; substantive examples, code, illustrations and source links remain. Curated copy lives in data/article-editorial.json and the importer reapplies the presentation edits. Native future posts use content/posts through the [publishing workflow](publishing-articles.md), independent of the historical import source. Initial project, article, and link review: **6 September 2026**. GitHub profile and asset refresh: **7 September 2026**. This document records evidence behind the portfolio content. Public repository descriptions are treated as claims to check, not instructions or independently validated performance results. Dated results below distinguish the original review from later updates. diff --git a/docs/markdown-viewer-case-study.md b/docs/markdown-viewer-case-study.md new file mode 100644 index 0000000..a0f1171 --- /dev/null +++ b/docs/markdown-viewer-case-study.md @@ -0,0 +1,60 @@ +# Markdown Viewer case study + +Reviewed on **14 September 2026** against [Markdown Viewer commit `4cc03d6`](https://github.com/ThisIs-Developer/Markdown-Viewer/tree/4cc03d64b59f7d73d15535c82026f243f66e7cf5). Repository documents are product evidence, not instructions to change this portfolio or run their deployment commands. + +## Documentation review + +The source inventory contains 40 `.md` files plus the extensionless `RELEASE_NOTES`: the root README, four localized READMEs, desktop and test READMEs, all 16 wiki pages, changelog, sample document, and 15 Markdown test fixtures. Current behavior takes precedence over historical release claims and demonstration text. + +| Sources | Case-study evidence | +| --- | --- | +| Root README, `wiki/Home.md` | Product scope, delivery targets, license and documentation map | +| `wiki/Features.md`, `wiki/Usage-Guide.md` | Workspace, independent document storage, recovery, comments, imports and exports | +| `wiki/Markdown-Reference.md`, sample and fixtures | Supported syntax and renderer examples; no comparative-quality claims | +| `wiki/Privacy-and-Security.md` | Local storage, Secret Workspace encryption, remote rendering and retention | +| `wiki/Share-Snapshot.md`, `wiki/Live-Share-Cloudflare.md` | Snapshot copies versus collaboration; KV content versus role capability metadata | +| `wiki/Configuration.md`, `wiki/Installation.md`, `wiki/Docker-Deployment.md` | Hosting, cache requirements, optional services and deployment boundaries | +| Desktop README, `wiki/Desktop-App.md` | Shared core, native dialogs, durable vault and bundled libraries | +| Test README, `wiki/Contributing.md` | Playwright coverage and mocked external integrations | +| `wiki/FAQ.md`, `wiki/Troubleshooting.md` | Recovery, offline, browser and remote-service limitations | +| `wiki/Localization.md`, four localized READMEs | Terminology and translated descriptions | +| `wiki/Development-Journey.md`, changelog, release notes | Project evolution, community attribution and 3.10.2 release themes | + +Implementation checks also consulted `package.json`, `workspace-storage.js`, `preview-worker.js`, desktop configuration, and the live-room Worker. The page's documentation links pin the reviewed commit; app and release links remain live. + +## Editorial choices + +- Credit Baivab as creator and maintainer, with community contributions. Avoid adoption counts, employment claims, exclusive authorship, or unmeasured performance claims. +- Replace the old generic page with seven sections: project, workspace, rendering, review/sharing, engineering, technology stack and project notes. +- Distinguish IndexedDB from the desktop Markdown vault; omit obsolete monolithic localStorage descriptions. +- Explain that comments stay out of exports and snapshots and synchronize separately during Live Share. +- Small snapshots contain compressed content in the URL fragment; larger snapshots use KV for 90 days. Live Share relays document/review content but persists role capability metadata. Links grant bearer access and sharing is not end-to-end encrypted. +- State remote-renderer and cache/bundling boundaries. Documentation has conflicting historical Docker-packaging and locale-count descriptions, so the page avoids a language count and blanket offline guarantees. +- Describe available application tests without claiming this portfolio change ran Markdown Viewer's suite or production integration tests. + +## Screenshot selection and provenance + +The author supplied screenshots captured **2 September 2026**, before the September 3 release. They illustrate the visible workflows, not that release's new recovery/export controls. Images are proportionally encoded as WebP at original dimensions and 800px width; no interface content was generated or retouched. The logo comes from `assets/icon.jpg` at the reviewed source commit. + +| File under `assets/work/markdown-viewer/` | Supplied screenshot | Placement | +| --- | --- | --- | +| `math.webp` | `Screenshot 2026-09-02 201128.png` | Main preview | +| `workspace.webp` | `Screenshot 2026-09-02 201025.png` | Workspace | +| `mermaid.webp` | `Screenshot 2026-09-02 204130.png` | Gallery | +| `model.webp` | `Screenshot 2026-09-02 203239.png` | Gallery | +| `music.webp` | `Screenshot 2026-09-02 203001.png` | Gallery | +| `mindmap.webp` | `Screenshot 2026-09-02 204224.png` | Gallery | +| `d2.webp` | `Screenshot 2026-09-02 204509.png` | More renderers | +| `timing.webp` | `Screenshot 2026-09-02 204416.png` | More renderers | + +The map screenshot visibly has an API-key warning. The second STL view has an extra close overlay, so the clean editor view is used. Other supplied captures repeat capabilities already represented. The metadata reference image informs the technology-label update and is not a product screenshot. + +## Maintenance + +`scripts/markdown-case-study.mjs` owns the case-study copy, section metadata and captions. `scripts/site-pages.mjs` selects it only for Markdown Viewer. Shared technology labels for all project metadata and full-stack lists use `scripts/technology-icons.mjs`. + +`work-case.css` loads on project readers only. `work-case.js` loads on Markdown Viewer only and enhances ordinary image links with a native dialog: keyboard entry, focus containment, Escape, close button, backdrop dismissal and focus return. Modified clicks retain native link behavior. Without JavaScript, original images and the native disclosure remain accessible. + +The old `overview`, `problem`, `contribution`, `inside`, and `project-notes` anchors remain available. Regenerate with `npm run build`; do not edit generated HTML. `scripts/work-case-checks.mjs` joins the existing cross-browser suite. + +See [icon sources](../assets/tools/SOURCES.md) and [verification results](verification.md). diff --git a/docs/screenshots/work-markdown-viewer-1440-dark.webp b/docs/screenshots/work-markdown-viewer-1440-dark.webp new file mode 100644 index 0000000..0d14a87 Binary files /dev/null and b/docs/screenshots/work-markdown-viewer-1440-dark.webp differ diff --git a/docs/screenshots/work-markdown-viewer-1440.webp b/docs/screenshots/work-markdown-viewer-1440.webp index 2df7e68..04bee81 100644 Binary files a/docs/screenshots/work-markdown-viewer-1440.webp and b/docs/screenshots/work-markdown-viewer-1440.webp differ diff --git a/docs/screenshots/work-markdown-viewer-390-dark.webp b/docs/screenshots/work-markdown-viewer-390-dark.webp new file mode 100644 index 0000000..10b9fc2 Binary files /dev/null and b/docs/screenshots/work-markdown-viewer-390-dark.webp differ diff --git a/docs/screenshots/work-markdown-viewer-390.webp b/docs/screenshots/work-markdown-viewer-390.webp index 33da0b2..3fd7580 100644 Binary files a/docs/screenshots/work-markdown-viewer-390.webp and b/docs/screenshots/work-markdown-viewer-390.webp differ diff --git a/docs/verification.md b/docs/verification.md index 677b6e5..c210d34 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -1,5 +1,23 @@ # Portfolio verification +## Markdown Viewer case study — 15 September 2026 + +The Markdown Viewer reader is rebuilt around seven sections and eight author-supplied screenshots. Project metadata and full technology lists use locally served brand marks and labeled Lucide symbols. The [source audit](markdown-viewer-case-study.md) records the documentation review, screenshot provenance and maintenance entry points. + +- The focused Chromium run passed **29 groups**, including all eight screenshot dialogs, keyboard focus containment and return, Escape, close/backdrop dismissal, the original-image link, and the JavaScript-free disclosure/image fallback. No browser exceptions occurred. +- **14 automated WCAG A/AA scans passed** across light/dark themes at 320, 390, 600, 768, 1024, 1440 and 1920px with the additional gallery expanded and all assets loaded. +- All twelve project readers passed icon/label loading checks. The geometry audit covered those readers at 320, 390, 768 and 1440px plus four landscape sizes: **96 states, zero geometry findings and zero browser errors**. +- Generated-file consistency, content validation, all 13 Quick Ask test groups and whitespace checks passed. The main browser suite now includes `scripts/work-case-checks.mjs`; its image audit distinguishes unloaded images inside a closed native disclosure from visible broken assets. + +The broader Chromium run finished with **58 passing checks and four Play Lab failures**: small mobile targets in light/dark mode, a synthetic pointer-capture error, and a blocked widget click. Separate checks against unmodified main at `3f8d72b` reproduced the target-size violations and pointer error, and the unchanged widget-control test also failed on an intercepted click there. These are not recorded as passes or addressed by this project-page change. CI reruns the existing Chromium/Firefox/WebKit workflow; local checks do not establish a deployed or cross-browser pass. + +Reports are under ignored `.qa-results/markdown-case-final`, `.qa-results/markdown-case-final-suite`, `.qa-results/markdown-case-responsive`, and the two `play-lab-*-baseline.json` reports. Fresh full-page previews: + +| Theme | Desktop | Mobile | +| --- | --- | --- | +| Light | [1440px](screenshots/work-markdown-viewer-1440.webp) | [390px](screenshots/work-markdown-viewer-390.webp) | +| Dark | [1440px](screenshots/work-markdown-viewer-1440-dark.webp) | [390px](screenshots/work-markdown-viewer-390-dark.webp) | + ## 9 September refinement The preceding revision removes decorative borders and dot bleed over controls, adds a Cloudflare Workers AI fact-selection endpoint, polishes all eleven local articles, introduces native article authoring, and replaces the playground and Interactions demos. diff --git a/scripts/build.mjs b/scripts/build.mjs index d05ce40..a0d9cf1 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -123,7 +123,7 @@ function metadata(options = {}) { ${options.page ? '' : ""} - + ${options.page === 'case-study' ? '\n ' : ""}${options.markdownCaseStudy ? '\n ' : ""} ${!options.page ? '' : ""} @@ -220,6 +220,8 @@ if (!check) { "cursor.css", "cursor.js", "editorial.css", + "work-case.css", + "work-case.js", "quick-ask.css", "quick-ask.js", "quick-ask-core.js", diff --git a/scripts/markdown-case-study.mjs b/scripts/markdown-case-study.mjs new file mode 100644 index 0000000..3ab807d --- /dev/null +++ b/scripts/markdown-case-study.mjs @@ -0,0 +1,98 @@ +import { technologyIcon, technologyList } from './technology-icons.mjs'; + +const repo = 'https://github.com/ThisIs-Developer/Markdown-Viewer'; +const docs = `${repo}/blob/4cc03d64b59f7d73d15535c82026f243f66e7cf5/wiki`; +const asset = '/assets/work/markdown-viewer'; +const icon = (name) => ``; +const arrow = ''; +const shots = { + math: ['Equations, in context', 'LaTeX source and live MathJax output in the same workspace.', 'Markdown Viewer split view with LaTeX source, the quadratic formula, binomial theorem and calculus in the preview.'], + workspace: ['A place for every document', 'Folders, open tabs, and a clear starting point for the next draft.', 'Markdown Viewer showing its document Explorer, Workspace and locked Secret Workspace alongside the Quick start screen.'], + mermaid: ['Diagrams from source', 'Mermaid turns a text definition into a readable database diagram.', 'Mermaid source beside a rendered entity relationship diagram connecting users, posts, tags and comments.'], + model: ['An extra dimension', 'Inspect STL geometry with solid, surface-angle, and wireframe views.', 'STL source in the editor alongside a blue three-dimensional model on a grid in the preview.'], + music: ['Notes become notation', 'ABCJS renders ABC notation as sheet music, with playback support.', 'ABC music notation source beside a rendered multi-part musical score in Markdown Viewer.'], + mindmap: ['Space to explore an idea', 'Markmap expands an outline into an interactive mind map.', 'Expanded Diagram Viewer showing a software development mind map with planning, development, testing and deployment branches.'], + timing: ['Timing, made visible', 'WaveDrom visualizes digital signals through the remote renderer.', 'WaveDrom timing diagram showing clock, reset, CPU, DMA, bus, cache, memory and interrupt signals.'], + d2: ['Architecture in a document', 'D2 diagrams render through Kroki and can be opened in a larger viewer.', 'D2 source and its enterprise banking architecture diagram in the editor and preview.'], +}; + +function screenshot(name, { hero = false, compact = false } = {}) { + const [title, caption, alt] = shots[name]; + const width = name === 'model' ? 1361 : 1366; + const height = name === 'model' ? 767 : 768; + return `
${alt}
${title}

${caption}

`; +} + +const sectionHeading = (number, label, title) => `

${number} ${label}

${title}

`; +const feature = (symbol, title, text) => `
${icon(symbol)}

${title}

${text}

`; + +export const markdownCaseEntries = [ + { id: 'overview', text: 'The project' }, { id: 'workspace', text: 'The workspace' }, + { id: 'rendering', text: 'Beyond plain text' }, { id: 'collaboration', text: 'Review & sharing' }, + { id: 'engineering', text: 'Engineering choices' }, { id: 'built-with', text: 'Built with' }, + { id: 'project-notes', text: 'Project notes' }, +]; + +export function renderMarkdownCaseStudy({ sidebar, next }) { + return `
${sidebar}
+
+

Developer tools · Open source

Actively maintained
+

Markdown Viewer

+

A home for your Markdown.
And everything it can become.

+

Write, organize, and preview technical documents in one local-first workspace. Bring diagrams, equations, 3D models, and review conversations into the same flow.

+ +
My role
Creator & maintainer
Period
2024–ongoing
Built with
${technologyList(['JavaScript', 'IndexedDB', 'Cloudflare'])}
+
+ ${screenshot('math', { hero: true })} +
+
${sectionHeading('01', 'THE PROJECT', 'One document. A complete workflow.')} +

It started with a simple idea: make Markdown easier to read, edit, and share.

Technical writing quickly outgrows a single preview pane. A README needs code examples. Research notes need equations. An architecture proposal needs diagrams and feedback. Moving between separate tools interrupts that work.

I created Markdown Viewer and maintain its editor, document storage, rendering pipeline, and web and desktop delivery. Community feedback and contributions have helped shape it into an open-source workspace for that whole process.

+
+
${sectionHeading('02', 'THE WORKSPACE', 'Pick up where you left off.')} +

A document explorer keeps files and folders together. Tabs, independent scroll positions, and Editor, Split, and Preview modes let the workspace follow the task.

+ ${screenshot('workspace')} +
+ ${feature('folder-open', 'Organize without friction', 'Nested folders, favorites, recent files, search, and bulk actions. Import a GitHub repository while preserving its Markdown directory structure.')} + ${feature('database', 'Save each document independently', 'Browser documents use IndexedDB; desktop documents live as ordinary Markdown files in a durable vault. Content loads when a document opens.')} + ${feature('history', 'Build recovery into the workflow', 'Recover deleted files from a 30-day Trash, preserve conflicting edits as copies, and export a folder-preserving workspace ZIP.')} + ${feature('lock-keyhole', 'Keep a private local space', 'Secret Workspace encrypts file contents and folder names with a password-derived AES-GCM key. The unlocked key stays in memory.')} +
+
+
${sectionHeading('03', 'BEYOND PLAIN TEXT', 'Write the source. See the idea.')} +

GitHub-style Markdown is the foundation. Rich code fences turn technical material into diagrams, charts, maps, models, and music, right beside the source.

+ +
${icon('workflow')}More from the rendering toolkit
+

Also supported PlantUML, Graphviz/DOT, Vega-Lite, GeoJSON, and TopoJSON, alongside MathJax for LaTeX.

Mermaid, math, maps, STL, ABC, and Markmap render in the client after their libraries load. PlantUML, D2, Graphviz, Vega-Lite, and WaveDrom use remote services; map tiles and some diagram previews also use the network.

+
+
${sectionHeading('04', 'REVIEW & SHARING', 'From your draft to a shared conversation.')} +

Feedback belongs next to the content it discusses. Anchored comments and threaded replies keep review attached to text, images, diagrams, and equations while leaving the Markdown source intact.

+
${icon('link')}

SHARE SNAPSHOT

Send a point-in-time copy.

Choose View only or Can edit. Small documents are compressed into the link; larger snapshots are stored in Cloudflare KV for 90 days. Recipients edit their own copy.

Snapshot details${arrow}
${icon('users')}

LIVE SHARE

Work on the same document.

Yjs synchronizes edits, cursors, and review threads through a Cloudflare Durable Object. Host, editor, and viewer capabilities are checked by the relay; viewers can still comment.

Collaboration details${arrow}
+

Snapshot and live invitations grant access to anyone holding the link. Live Share is not end-to-end encrypted. The relay does not persist document or comment content, but it does retain role capability metadata. Comments stay out of exports and snapshot links.

+
+
${sectionHeading('05', 'ENGINEERING CHOICES', 'More capability. A focused core.')} +

The application stays mostly vanilla JavaScript. The work is in how storage, parsing, rendering, and export fit together.

+
  1. ${icon('file-code-2')}Markdown sourceA plain-text editor
  2. ${icon('shield-check')}Parse & sanitizeMarked + DOMPurify
  3. ${icon('panels-top-left')}Preview & exportRich-content preparation
+
01

Keep typing responsive

Size-aware debouncing, optional Web Worker parsing for large documents, cached blocks, and selective DOM updates reduce repeated work. Heavy renderer libraries load when needed.

02

Make the exported document complete

PDF and PNG use an off-screen snapshot that waits for fonts, images, math, and visual renderers. Markdown and standalone HTML offer other ways out, with remembered appearance choices for visual exports.

03

Share a core across web and desktop

A Neutralinojs wrapper reuses the web application, adds native file dialogs and a durable vault, and bundles renderer libraries. The web app also supports PWA installation and static or Docker hosting.

04

Check the workflows that matter

The repository includes Playwright coverage for editing, rich rendering, import/export, storage, sharing, keyboard interaction, and responsive layouts, plus Chromium, Firefox, and WebKit smoke tests. External integrations are mocked in the local suite.

+
+
${sectionHeading('06', 'BUILT WITH', 'The tools behind the workspace.')} +
${[ + ['JavaScript', 'Application core', 'Editor behavior, preview orchestration, and browser APIs.'], + ['IndexedDB', 'Local persistence', 'Independent document records, metadata, and encrypted storage.'], + ['Cloudflare', 'Optional connected features', 'Pages hosting, KV snapshots, and Durable Object live rooms.'], + ['Neutralinojs', 'Desktop delivery', 'A shared web core with native dialogs and a durable file vault.'], + ['Docker', 'Self-hosting', 'An Nginx container for serving the static web application.'], + ['Playwright', 'Workflow verification', 'Browser automation for document, rendering, and interaction flows.'], + ].map(([name, purpose, description]) => `
${technologyIcon(name)}

${name}

${purpose}

${description}

`).join('')}
+
+
${sectionHeading('07', 'PROJECT NOTES', 'Built in public. Still moving forward.')} +

Markdown Viewer has grown from a small preview tool into a document workspace through ongoing iteration and community contributions. The recent 3.10.2 release focuses on recovery, rendering correctness, review stability, and complete visual exports.

+

Its local-first approach keeps everyday writing on the device. Connected features have explicit service boundaries, and offline behavior depends on cached or bundled assets. The documentation records those tradeoffs alongside current capabilities.

+ +

Project documentation reviewed September 14, 2026. Product screenshots supplied by the author, captured September 2, 2026. Markdown Viewer is licensed under Apache 2.0.

+
+

Make room for your next idea.

Open the workspace and start with a document.

Try Markdown Viewer${arrow}
+
+ Next project${next.title}${arrow} +

Project screenshot

+
`; +} diff --git a/scripts/site-pages.mjs b/scripts/site-pages.mjs index 7148a77..728e59e 100644 --- a/scripts/site-pages.mjs +++ b/scripts/site-pages.mjs @@ -1,6 +1,8 @@ import { renderPlayground } from "./playground.mjs"; import { articleArt, articleCategory } from "./editorial.mjs"; import { enterpriseArt } from "./enterprise-art.mjs"; +import { technologyList } from "./technology-icons.mjs"; +import { markdownCaseEntries, renderMarkdownCaseStudy } from "./markdown-case-study.mjs"; const esc = (value = "") => String(value).replace( @@ -45,7 +47,7 @@ const category = (p) => ? "Tools" : "Web apps"; const metaPanel = (entries) => - `
${entries.map(([label, value]) => `
${esc(label)}
${esc(value)}
`).join("")}
`; + `
${entries.map(([label, value]) => `
${esc(label)}
${Array.isArray(value) ? technologyList(value) : esc(value)}
`).join("")}
`; function toc(entries, back = "/work", label = "Back to work") { if (!entries.length) return ``; @@ -112,14 +114,15 @@ export function renderSitePages(data, ui) { [ ["Project", p.status], ["Period", p.year], - ["Built with", p.stack.slice(0, 3).join(", ")], + ["Built with", p.stack.slice(0, 3)], ], - )}
${projectImage(p, true)}${!p.image ? "
Project image placeholder
" : ""}
${p.live ? link(p.live, p.liveLabel || "Visit project", "button") : ""}${p.source ? link(p.source, "Source code") : ""}

Overview

${esc(p.summary)}

${esc(p.contribution)}

The problem

${esc(p.problem)}

My contribution

${esc(p.contribution)}

${p.stack.map((s) => `${esc(s)}`).join("")}

Inside the build

    ${p.features.map((f) => `
  • ${esc(f)}
  • `).join("")}
${["markdown-viewer", "medichain", "notemarker"].includes(p.id) ? `` : ""}

Project notes

${esc(p.status)}

${esc(p.note || `${p.title} is part of my work in ${p.category.toLowerCase()}. The links above provide the available project and implementation details.`)}

Next project${esc(next.title)}${arrow}`; - pages[`work/${p.id}.html`] = shell(content, { + )}
${projectImage(p, true)}${!p.image ? "
Project image placeholder
" : ""}
${p.live ? link(p.live, p.liveLabel || "Visit project", "button") : ""}${p.source ? link(p.source, "Source code") : ""}

Overview

${esc(p.summary)}

${esc(p.contribution)}

The problem

${esc(p.problem)}

My contribution

${esc(p.contribution)}

${technologyList(p.stack)}

Inside the build

    ${p.features.map((f) => `
  • ${esc(f)}
  • `).join("")}
${["markdown-viewer", "medichain", "notemarker"].includes(p.id) ? `` : ""}

Project notes

${esc(p.status)}

${esc(p.note || `${p.title} is part of my work in ${p.category.toLowerCase()}. The links above provide the available project and implementation details.`)}

Next project${esc(next.title)}${arrow}`; + pages[`work/${p.id}.html`] = shell(p.id === "markdown-viewer" ? renderMarkdownCaseStudy({ sidebar: toc(markdownCaseEntries), next }) : content, { path: `/work/${p.id}`, title: `${p.title} — Baivab Sarkar`, description: p.summary, page: "case-study", + markdownCaseStudy: p.id === "markdown-viewer", }); } const privateEntries = enterprise.projects.map((x) => ({ diff --git a/scripts/technology-icons.mjs b/scripts/technology-icons.mjs new file mode 100644 index 0000000..c879842 --- /dev/null +++ b/scripts/technology-icons.mjs @@ -0,0 +1,27 @@ +const escape = (value) => String(value).replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + +const brands = { + JavaScript: 'javascript', Python: 'python', Solidity: 'solidity', Ethereum: 'ethereum', + Cloudflare: 'cloudflare', Neutralinojs: 'neutralinojs', Playwright: 'playwright', + Docker: 'docker', Spring: 'spring', Redis: 'redis', PostgreSQL: 'postgresql', + LangChain: 'langchain', Streamlit: 'streamlit', Django: 'django', Selenium: 'selenium', + MediaPipe: 'mediapipe', OpenCV: 'opencv', TensorFlow: 'tensorflow', Bootstrap: 'bootstrap', + 'Llama 2': 'meta', 'Web3.js': 'web3dotjs', 'scikit-learn': 'scikitlearn', MySQL: 'mysql', +}; +const symbols = { + IndexedDB: 'database', 'Browser Storage': 'database', localStorage: 'database', + 'Web development': 'globe', WebExtensions: 'panels-top-left', 'Canvas API': 'paintbrush', + FAISS: 'database', Chainlit: 'messages-square', MetaMask: 'wallet', JPA: 'database', + BeautifulSoup: 'code-xml', +}; + +export function technologyIcon(name) { + if (name === 'HTML & CSS') return ''; + const brand = brands[name]; + const path = brand ? `/assets/tools/${brand}.svg` : `/assets/tools/lucide/${symbols[name] || 'code-xml'}.svg`; + return ``; +} + +export function technologyList(stack) { + return `
    ${stack.map((name) => `
  • ${technologyIcon(name)}${escape(name)}
  • `).join('')}
`; +} diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 5beaffa..d1f7e1e 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -13,6 +13,7 @@ import { playgroundWidgetChecks } from "./playground-widget-checks.mjs"; import { playgroundThemeChecks } from "./playground-theme-checks.mjs"; import { themeChecks } from "./theme-checks.mjs"; import { walletChecks } from "./wallet-checks.mjs"; +import { workCaseChecks } from "./work-case-checks.mjs"; const args = process.argv.slice(2); const option = (name, fallback) => @@ -185,7 +186,7 @@ async function inspectLayout(page, noJs = false) { brokenImages: [...document.images] .filter( (image) => - image.getClientRects().length && + image.checkVisibility() && (!image.complete || !image.naturalWidth), ) .map((image) => image.currentSrc || image.src), @@ -1466,6 +1467,7 @@ try { ["game start, pause, reset and keyboard", () => game(page)], ["clipboard success and denial", () => clipboard(browser)], ["work and blog search, filters and sort", () => archive(page)], + ["Markdown Viewer screenshots, keyboard focus and responsive gallery", () => workCaseChecks(page, load)], [ "all project and article readers and playable 404", () => readers(page), diff --git a/scripts/work-case-checks.mjs b/scripts/work-case-checks.mjs new file mode 100644 index 0000000..49eed88 --- /dev/null +++ b/scripts/work-case-checks.mjs @@ -0,0 +1,53 @@ +import assert from 'node:assert/strict'; + +export async function workCaseChecks(page, load) { + await page.setViewportSize({ width: 1440, height: 900 }); + await load(page, '/work/markdown-viewer'); + const dialog = page.getByRole('dialog'); + const screenshots = page.locator('[data-screenshot]'); + assert.equal(await screenshots.count(), 8, 'Eight curated product screenshots'); + assert.equal(await page.locator('.mv-meta .technology-list li').count(), 3); + assert.equal(await page.locator('.mv-stack-item').count(), 6); + const anchors = await page.locator('.reading-toc a').evaluateAll(links => links.map(link => link.hash.slice(1))); + for (const id of anchors) assert(await page.locator(`#${id}`).isVisible(), `Section ${id} exists`); + await page.locator('.mv-more summary').click(); + assert(await page.locator('.mv-more').evaluate(element => element.open)); + for (const link of await screenshots.all()) { + await link.scrollIntoViewIfNeeded(); + await link.locator(':scope > img').evaluate(image => image.decode()); + const expectedSrc = await link.getAttribute('href'); + const expectedAlt = await link.locator(':scope > img').getAttribute('alt'); + await link.press('Enter'); + assert(await dialog.isVisible(), 'Screenshot opens from the keyboard'); + assert.equal(await dialog.locator('.mv-lightbox-image img').getAttribute('src'), new URL(expectedSrc, page.url()).href); + assert.equal(await dialog.locator('.mv-lightbox-image img').getAttribute('alt'), expectedAlt); + assert(await dialog.evaluate(element => element.contains(document.activeElement)), 'Focus enters the dialog'); + await page.keyboard.press('Tab'); + assert(await dialog.evaluate(element => element.contains(document.activeElement)), 'Focus stays within the modal'); + await page.keyboard.press('Tab'); + assert(await dialog.evaluate(element => element.contains(document.activeElement)), 'Tab cycles within the modal'); + await page.keyboard.press('Shift+Tab'); + assert(await dialog.evaluate(element => element.contains(document.activeElement)), 'Reverse Tab cycles within the modal'); + assert.equal(await dialog.locator('[data-original-screenshot]').getAttribute('href'), new URL(expectedSrc, page.url()).href); + await page.keyboard.press('Escape'); + assert.equal(await dialog.isVisible(), false); + assert(await link.evaluate(element => element === document.activeElement), 'Focus returns to the screenshot'); + } + await screenshots.first().click(); + await page.getByRole('button', { name: 'Close screenshot', exact: true }).click(); + assert.equal(await dialog.isVisible(), false); + assert.equal(await page.locator('html').evaluate(element => element.classList.contains('mv-screenshot-open')), false); + await screenshots.first().click(); + await page.mouse.click(1, 1); + assert.equal(await dialog.isVisible(), false, 'Backdrop dismisses the screenshot'); + for (const width of [320, 390, 768, 1440]) { + await page.setViewportSize({ width, height: 900 }); + await screenshots.last().scrollIntoViewIfNeeded(); + assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Expanded gallery fits ${width}px`); + await screenshots.last().click(); + const bounds = await dialog.boundingBox(); + assert(bounds.x >= 0 && bounds.x + bounds.width <= width, `Screenshot dialog fits ${width}px`); + await page.keyboard.press('Escape'); + } + await page.locator('.mv-more summary').click(); +} diff --git a/work-case.css b/work-case.css new file mode 100644 index 0000000..c9b8cc7 --- /dev/null +++ b/work-case.css @@ -0,0 +1,201 @@ +/* Project metadata uses real brand marks and Lucide symbols for web APIs. */ +.technology-list { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px 18px; + list-style: none; + padding: 0; + margin: 0; +} +.technology-list li { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--ink); + font-size: 12px; + line-height: 1.4; + margin: 0; + padding: 0; +} +.tech-icon { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 5px; + border-radius: 8px; + background: #fff; +} +.tech-icon img { display: block; width: 100%; height: 100%; object-fit: contain; margin: 0; border-radius: 0; } +.prose .technology-list { padding: 0; margin: 28px 0; } +.prose .technology-list li { margin: 0; padding: 0; font-size: 14px; } +.tech-icon-pair { width: 44px; gap: 1px; } +.tech-icon-pair img { width: 50%; } +.detail-meta:has(.technology-list) { max-width: none; grid-template-columns: 1fr 1fr 1.7fr; } +.mv-layout { max-width: 1460px; grid-template-columns: 172px minmax(0, 1fr); gap: 54px; } +.mv-case { min-width: 0; } +.mv-kicker { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 30px; } +.mv-kicker .detail-category { margin: 0; font-size: 11px; letter-spacing: .02em; } +.mv-status { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); font-size: 11px; } +.mv-status > span { width: 6px; height: 6px; border-radius: 50%; background: #238343; } +.mv-title { display: flex; align-items: center; gap: 20px; } +.mv-title > img { width: 52px; height: 52px; border-radius: 12px; flex-shrink: 0; } +.mv-header h1 { font-size: clamp(36px, 4.4vw, 62px); letter-spacing: -.04em; line-height: 1.1; } +.mv-header .reading-deck { margin-top: 28px; font-size: clamp(28px, 3vw, 43px); line-height: 1.12; letter-spacing: -.035em; color: var(--ink); } +.mv-header .reading-deck em { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; color: var(--muted); } +.mv-intro { max-width: 650px; margin: 22px 0 0; font-size: 16px; line-height: 1.8; color: var(--muted); } +.mv-header .case-actions { margin: 27px 0 35px; gap: 24px; } +.mv-header .case-actions .text-link { display: inline-flex; align-items: center; gap: 9px; font-size: 13px; } +.mv-symbol { width: 22px; height: 22px; flex: 0 0 auto; } +[data-theme="dark"] .mv-symbol { filter: invert(1); } +.mv-meta { display: grid; grid-template-columns: 1fr .85fr 2fr; gap: 25px; margin: 0 0 34px; padding-top: 24px; border-top: 1px solid var(--line); } +.mv-meta > div { min-width: 0; } +.mv-meta dt { text-transform: uppercase; letter-spacing: .09em; font-size: 10px; color: var(--muted); margin-bottom: 10px; } +.mv-meta dd { margin: 0; font-size: 13px; line-height: 1.6; color: var(--ink); } +.mv-meta .technology-list { gap: 10px 14px; } +.mv-meta .tech-icon { width: 27px; height: 27px; padding: 4px; border-radius: 6px; } +.mv-shot { margin: 0; min-width: 0; } +.mv-image-link { display: block; position: relative; overflow: hidden; border-radius: 13px; background: #eef0f3; border: 1px solid var(--line); } +.mv-image-link > img { display: block; width: 100%; height: auto; margin: 0; } +.mv-enlarge { position: absolute; bottom: 10px; right: 10px; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 9px; background: #fff; box-shadow: 0 2px 12px #0002; transition: transform 160ms ease; } +.mv-enlarge .mv-symbol { width: 16px; height: 16px; filter: none; } +.mv-image-link:hover .mv-enlarge { transform: scale(1.08); } +.mv-shot figcaption { padding: 17px 2px 0; } +.mv-shot figcaption > span { font-size: 15px; font-weight: 600; color: var(--ink); } +.mv-shot figcaption p { margin: 5px 0 0; font-size: 13px; line-height: 1.65; color: var(--muted); } +.mv-hero { padding: 13px; border-radius: 22px; background: var(--strong-glass); } +.mv-hero figcaption { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; padding: 15px 5px 4px; } +.mv-hero figcaption > span { font-size: 12px; } +.mv-hero figcaption p { font-size: 11px; margin: 0; } +.mv-content { font-size: 16px; line-height: 1.8; color: var(--muted); } +.mv-content section { margin: 86px 0 0; scroll-margin-top: 105px; } +.mv-content p { margin: 0 0 18px; } +.mv-content h2 { font-family: 'Instrument Sans', sans-serif; color: var(--ink); font-size: clamp(27px, 2.5vw, 35px); font-weight: 550; line-height: 1.2; letter-spacing: -.045em; } +.mv-content h3 { font-family: 'Instrument Sans', sans-serif; color: var(--ink); font-size: 17px; line-height: 1.4; font-weight: 600; letter-spacing: -.025em; } +.mv-section-heading { margin-bottom: 27px; } +.mv-section-heading .eyebrow { font-size: 10px; letter-spacing: .1em; margin-bottom: 13px; } +.mv-section-heading .eyebrow > span { color: var(--blue); margin-right: 10px; } +.mv-section-intro { max-width: 700px; } +.mv-overview { display: grid; grid-template-columns: .9fr 1.4fr; gap: 48px; } +.mv-content .mv-lead { font-family: 'Instrument Serif', Georgia, serif; font-size: 31px; line-height: 1.3; letter-spacing: -.025em; color: var(--ink); } +.mv-content > section > .mv-shot { margin: 28px 0; } +.mv-feature-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px 32px; margin-top: 34px; } +.mv-feature { display: flex; align-items: flex-start; gap: 14px; } +.mv-feature > .mv-symbol { margin-top: 3px; opacity: .7; } +.mv-feature h3 { margin-bottom: 8px; } +.mv-feature p { font-size: 14px; line-height: 1.8; margin: 0; } +.mv-gallery { display: grid; grid-template-columns: 1fr 1fr; gap: 32px 22px; margin: 30px 0; } +.mv-gallery .mv-shot figcaption p { margin: 6px 0 0; } +.mv-more { margin-top: 30px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); } +.mv-more summary { cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 18px 0; list-style: none; font-size: 14px; color: var(--ink); } +.mv-more summary::-webkit-details-marker { display: none; } +.mv-more summary > span:first-child { display: inline-flex; align-items: center; gap: 12px; } +.mv-details-plus { font-size: 22px; line-height: 1; } +.mv-more[open] .mv-details-plus { transform: rotate(45deg); } +.mv-renderer-note { margin-top: 24px; font-size: 13px; line-height: 1.8; } +.mv-renderer-note strong { color: var(--ink); margin-right: 8px; } +.mv-renderer-note p:last-child { margin-bottom: 0; } +.mv-share-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 30px 0 22px; } +.mv-share-grid > div { background: var(--strong-glass); border-radius: 18px; padding: 28px; display: flex; flex-direction: column; align-items: flex-start; } +.mv-share-grid > div > .mv-symbol { width: 26px; height: 26px; margin-bottom: 23px; } +.mv-share-grid .eyebrow { font-size: 10px; letter-spacing: .08em; margin-bottom: 8px; } +.mv-share-grid h3 { font-size: 20px; margin-bottom: 13px; } +.mv-share-grid p { font-size: 14px; line-height: 1.85; } +.mv-share-grid .text-link { margin-top: auto; font-size: 12px; } +.mv-content .mv-footnote { font-size: 12px; line-height: 1.85; } +.mv-pipeline { display: grid; grid-template-columns: 1fr 1fr 1fr; list-style: none; padding: 26px 0; margin: 30px 0 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); } +.mv-pipeline li { position: relative; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 5px; padding: 0 14px; } +.mv-pipeline li + li::before { content: '→'; position: absolute; left: -7px; top: 30%; color: var(--muted); } +.mv-pipeline .mv-symbol { margin-bottom: 7px; } +.mv-pipeline strong { font-size: 13px; font-weight: 600; color: var(--ink); } +.mv-pipeline span { font-size: 11px; } +.mv-engineering-list > div { display: flex; align-items: baseline; gap: 24px; padding: 25px 0; border-bottom: 1px solid var(--line); } +.mv-engineering-list > div > span { font-size: 11px; color: var(--muted); } +.mv-engineering-list h3 { margin-bottom: 8px; } +.mv-engineering-list p { margin: 0; font-size: 14px; } +.mv-stack { display: grid; grid-template-columns: 1fr 1fr; gap: 28px 35px; } +.mv-stack-item { display: flex; align-items: flex-start; gap: 16px; } +.mv-stack-item > .tech-icon { width: 46px; height: 46px; padding: 9px; border-radius: 12px; } +.mv-stack-item h3 { font-size: 16px; margin: 0; } +.mv-stack-item > div > span { font-size: 11px; } +.mv-stack-item p { font-size: 13px; line-height: 1.8; margin: 7px 0 0; } +.mv-source-links { display: flex; flex-wrap: wrap; gap: 17px 26px; padding: 12px 0 27px; } +.mv-source-links a { font-size: 12px; } +.mv-closing { display: flex; align-items: center; gap: 18px; background: var(--strong-glass); border-radius: 20px; padding: 28px; margin: 42px 0 0; } +.mv-closing > img { border-radius: 11px; flex-shrink: 0; } +.mv-closing h2 { font-size: 20px; letter-spacing: -.025em; } +.mv-closing p { font-size: 12px; margin: 6px 0 0; } +.mv-closing .button { margin-left: auto; flex-shrink: 0; font-size: 12px; } +.mv-screenshot-open { overflow: hidden; } +.mv-lightbox { width: min(1450px, calc(100vw - 48px)); max-width: none; max-height: calc(100dvh - 48px); margin: auto; padding: 0; border: 1px solid var(--line); border-radius: 16px; background: var(--paper); color: var(--ink); box-shadow: 0 20px 90px #0004; } +.mv-lightbox::backdrop { background: #0c1014d9; } +.mv-lightbox-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 18px; } +.mv-lightbox-bar h2 { font: 500 14px/1.4 'Instrument Sans', sans-serif; } +.mv-lightbox button { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border: 0; border-radius: 9px; background: var(--strong-glass); cursor: pointer; } +.mv-lightbox-image { overflow: auto; background: #eef0f3; } +.mv-lightbox-image img { display: block; width: 100%; max-height: calc(100dvh - 170px); object-fit: contain; } +.mv-lightbox-footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 18px; font-size: 11px; color: var(--muted); } +.mv-lightbox-footer a { display: inline-flex; align-items: center; gap: 7px; min-height: 30px; color: var(--ink); } +.mv-lightbox-footer .icon { width: 12px; height: 12px; } +.mv-lightbox-footer p { margin: 0; } +@media (max-width: 1100px) { + .mv-layout { gap: 34px; grid-template-columns: 155px minmax(0, 1fr); padding-left: 34px; padding-right: 34px; } + .mv-meta { grid-template-columns: 1fr 1fr; } + .mv-meta > div:last-child { grid-column: 1 / -1; } + .mv-overview { grid-template-columns: 1fr; gap: 8px; } + .mv-closing { flex-wrap: wrap; } + .mv-closing .button { margin-left: 66px; } + .mv-hero figcaption { display: block; } + .mv-hero figcaption p { margin-top: 4px; } +} +@media (max-width: 767px) { + .detail-meta:has(.technology-list) { grid-template-columns: 1fr 1fr; } + .detail-meta:has(.technology-list) > div:last-child { grid-column: 1 / -1; } + .mv-layout { display: block; padding: 115px 24px 60px; } + .mv-kicker { align-items: flex-start; margin-bottom: 26px; } + .mv-kicker .detail-category, .mv-status { font-size: 10px; } + .mv-title { gap: 13px; } + .mv-title > img { width: 40px; height: 40px; border-radius: 9px; } + .mv-header h1 { font-size: clamp(29px, 6.2vw, 43px); } + .mv-header .reading-deck { margin-top: 24px; font-size: 33px; } + .mv-intro { font-size: 15px; line-height: 1.75; } + .mv-meta { gap: 20px; padding-top: 22px; } + .mv-header .case-actions { gap: 20px; margin-bottom: 28px; } + .mv-header .button { font-size: 12px; } + .mv-hero { padding: 7px; border-radius: 16px; } + .mv-image-link { border-radius: 10px; } + .mv-hero figcaption { padding: 12px 6px 5px; } + .mv-content { font-size: 15px; } + .mv-content section { margin-top: 65px; } + .mv-content h2 { font-size: 29px; } + .mv-content .mv-lead { font-size: 28px; } + .mv-feature-grid { gap: 26px; } + .mv-gallery { gap: 27px 20px; } + .mv-share-grid > div { padding: 24px; } + .mv-stack { gap: 26px; } + .mv-closing { padding: 24px; } + .mv-closing h2 { font-size: 19px; } + .mv-lightbox { width: calc(100vw - 20px); max-height: calc(100dvh - 20px); border-radius: 12px; } +} +@media (max-width: 600px) { + .mv-feature-grid, .mv-gallery, .mv-share-grid, .mv-stack { grid-template-columns: 1fr; } + .mv-kicker { flex-direction: column; gap: 10px; } + .mv-header .reading-deck { font-size: 30px; } + .mv-shot figcaption > span { font-size: 14px; } + .mv-shot figcaption p { font-size: 12px; } + .mv-feature-grid { margin-top: 30px; } + .mv-pipeline { grid-template-columns: 1fr; padding: 10px 0; } + .mv-pipeline li { align-items: flex-start; text-align: left; padding: 20px 10px 20px 52px; } + .mv-pipeline .mv-symbol { position: absolute; left: 10px; top: 23px; } + .mv-pipeline li + li::before { content: '↓'; top: -12px; left: 16px; } + .mv-closing { display: grid; grid-template-columns: 40px 1fr; gap: 18px 14px; } + .mv-closing > img { width: 40px; height: 40px; } + .mv-closing .button { grid-column: 1 / -1; margin: 0; justify-self: start; } + .mv-content .mv-closing h2 { font-size: 20px; } +} +@media (prefers-reduced-motion: reduce) { + .mv-enlarge { transition: none; } +} diff --git a/work-case.js b/work-case.js new file mode 100644 index 0000000..6d9ecb9 --- /dev/null +++ b/work-case.js @@ -0,0 +1,45 @@ +/* Screenshot links remain usable when JavaScript or dialog support is absent. */ +(() => { + const dialog = document.querySelector('.mv-lightbox'); + if (!dialog || typeof dialog.showModal !== 'function') return; + const image = dialog.querySelector('.mv-lightbox-image img'); + const title = dialog.querySelector('#mv-lightbox-title'); + const original = dialog.querySelector('[data-original-screenshot]'); + const close = dialog.querySelector('[data-close-screenshot]'); + let opener; + document.querySelectorAll('[data-screenshot]').forEach(link => { + link.addEventListener('click', event => { + if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; + event.preventDefault(); + opener = link; + image.src = link.href; + original.href = link.href; + image.alt = link.querySelector('img').alt; + title.textContent = link.dataset.caption; + dialog.showModal(); + document.documentElement.classList.add('mv-screenshot-open'); + }); + }); + close.addEventListener('click', () => dialog.close()); + dialog.addEventListener('keydown', event => { + if (event.key !== 'Tab') return; + if (event.shiftKey && document.activeElement === close) { + event.preventDefault(); + original.focus(); + } else if (!event.shiftKey && document.activeElement === original) { + event.preventDefault(); + close.focus(); + } + }); + dialog.addEventListener('click', event => { + if (event.target !== dialog) return; + const rect = dialog.getBoundingClientRect(); + if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close(); + }); + dialog.addEventListener('close', () => { + document.documentElement.classList.remove('mv-screenshot-open'); + image.removeAttribute('src'); + original.removeAttribute('href'); + opener?.focus({ preventScroll: true }); + }); +})(); diff --git a/work/ams.html b/work/ams.html index 14f0061..e2ff241 100644 --- a/work/ams.html +++ b/work/ams.html @@ -32,8 +32,9 @@ + -

Full-stack · Team project

AMS

Role-based attendance workflows for administrators, teachers and students.

Project
Team prototype
Period
2024
Built with
JavaScript, Spring, Redis
Academic Management System attendance dashboard on two mobile screens.

Overview

Role-based attendance workflows for administrators, teachers and students.

Contributed to a five-person academic management project.

The problem

Academic attendance records need coordinated access and reporting across institution roles.

My contribution

Contributed to a five-person academic management project.

JavaScriptSpringRedisPostgreSQLJPA

Inside the build

  • Role-specific attendance interfaces.
  • Attendance recording and report workflows.

Project notes

Team prototype

AMS is part of my work in full-stack · team project. The links above provide the available project and implementation details.

Next projectNoteMarker Extension

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Full-stack · Team project

AMS

Role-based attendance workflows for administrators, teachers and students.

Project
Team prototype
Period
2024
Built with
  • JavaScript
  • Spring
  • Redis
Academic Management System attendance dashboard on two mobile screens.

Overview

Role-based attendance workflows for administrators, teachers and students.

Contributed to a five-person academic management project.

The problem

Academic attendance records need coordinated access and reporting across institution roles.

My contribution

Contributed to a five-person academic management project.

  • JavaScript
  • Spring
  • Redis
  • PostgreSQL
  • JPA

Inside the build

  • Role-specific attendance interfaces.
  • Attendance recording and report workflows.

Project notes

Team prototype

AMS is part of my work in full-stack · team project. The links above provide the available project and implementation details.

Next projectNoteMarker Extension

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/body-language.html b/work/body-language.html index 2129a68..c7e2d79 100644 --- a/work/body-language.html +++ b/work/body-language.html @@ -32,8 +32,9 @@ + -

Computer vision · Notebook

Body Language Detection

A computer-vision experiment comparing landmark-based classification pipelines.

Project
Archived research experiment
Period
2023
Built with
Python, MediaPipe, OpenCV
Bar chart of labeled samples from the Body Language Detection experiment.

Overview

A computer-vision experiment comparing landmark-based classification pipelines.

Built notebooks for collecting labeled landmark data, training classifiers and exporting models.

The problem

Explore how video landmarks can be collected and used as classifier inputs.

My contribution

Built notebooks for collecting labeled landmark data, training classifiers and exporting models.

PythonMediaPipeOpenCVscikit-learnTensorFlow

Inside the build

  • Collect labeled landmark data from webcam or recorded video.
  • Compare scikit-learn pipelines and a TensorFlow Lite export.

Project notes

Archived research experiment

Body Language Detection is part of my work in computer vision · notebook. The links above provide the available project and implementation details.

Next projectMediChain

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Computer vision · Notebook

Body Language Detection

A computer-vision experiment comparing landmark-based classification pipelines.

Project
Archived research experiment
Period
2023
Built with
  • Python
  • MediaPipe
  • OpenCV
Bar chart of labeled samples from the Body Language Detection experiment.

Overview

A computer-vision experiment comparing landmark-based classification pipelines.

Built notebooks for collecting labeled landmark data, training classifiers and exporting models.

The problem

Explore how video landmarks can be collected and used as classifier inputs.

My contribution

Built notebooks for collecting labeled landmark data, training classifiers and exporting models.

  • Python
  • MediaPipe
  • OpenCV
  • scikit-learn
  • TensorFlow

Inside the build

  • Collect labeled landmark data from webcam or recorded video.
  • Compare scikit-learn pipelines and a TensorFlow Lite export.

Project notes

Archived research experiment

Body Language Detection is part of my work in computer vision · notebook. The links above provide the available project and implementation details.

Next projectMediChain

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/csv-chatbot.html b/work/csv-chatbot.html index 4cd414c..4f1eba9 100644 --- a/work/csv-chatbot.html +++ b/work/csv-chatbot.html @@ -32,8 +32,9 @@ + -

AI · Retrieval experiment

Llama 2 GGML CSV Chatbot

Ask questions of uploaded CSV data using local model inference and vector retrieval.

Project
Research prototype
Period
2024
Built with
Python, LangChain, Streamlit
Llama 2 GGML CSV Chatbot — replaceable project image
Project image placeholder

Overview

Ask questions of uploaded CSV data using local model inference and vector retrieval.

Built a Streamlit prototype combining CSV ingestion, embedding search and a quantized Llama 2 model.

The problem

Exploring tabular data through natural language requires grounding responses in the uploaded file.

My contribution

Built a Streamlit prototype combining CSV ingestion, embedding search and a quantized Llama 2 model.

PythonLangChainStreamlitLlama 2FAISS

Inside the build

  • Chunk CSV data and index it with sentence-transformer embeddings and FAISS.
  • Retrieve relevant rows for a Llama 2 question-answering workflow.

Project notes

Research prototype

Llama 2 GGML CSV Chatbot is part of my work in ai · retrieval experiment. The links above provide the available project and implementation details.

Next projectNews Scraping Platform

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

AI · Retrieval experiment

Llama 2 GGML CSV Chatbot

Ask questions of uploaded CSV data using local model inference and vector retrieval.

Project
Research prototype
Period
2024
Built with
  • Python
  • LangChain
  • Streamlit
Llama 2 GGML CSV Chatbot — replaceable project image
Project image placeholder

Overview

Ask questions of uploaded CSV data using local model inference and vector retrieval.

Built a Streamlit prototype combining CSV ingestion, embedding search and a quantized Llama 2 model.

The problem

Exploring tabular data through natural language requires grounding responses in the uploaded file.

My contribution

Built a Streamlit prototype combining CSV ingestion, embedding search and a quantized Llama 2 model.

  • Python
  • LangChain
  • Streamlit
  • Llama 2
  • FAISS

Inside the build

  • Chunk CSV data and index it with sentence-transformer embeddings and FAISS.
  • Retrieve relevant rows for a Llama 2 question-answering workflow.

Project notes

Research prototype

Llama 2 GGML CSV Chatbot is part of my work in ai · retrieval experiment. The links above provide the available project and implementation details.

Next projectNews Scraping Platform

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/enterprise.html b/work/enterprise.html index 9a1b026..08495d4 100644 --- a/work/enterprise.html +++ b/work/enterprise.html @@ -32,6 +32,7 @@ + diff --git a/work/markdown-viewer.html b/work/markdown-viewer.html index d4d4263..b19a3c9 100644 --- a/work/markdown-viewer.html +++ b/work/markdown-viewer.html @@ -32,8 +32,63 @@ + + -

Developer tools · Open source

Markdown Viewer

A local-first Markdown workspace, from the first draft to the shared document.

Project
Actively maintained
Period
2024–2026
Built with
JavaScript, IndexedDB, Cloudflare
Markdown Viewer workspace showing its document explorer, editor and rendered preview.

Overview

A local-first Markdown workspace, from the first draft to the shared document.

Created and maintain the editor, its document workspace, rendering workflows and web/desktop delivery.

The problem

Technical writing needs more than a preview: documents, diagrams, review and sharing should work together.

My contribution

Created and maintain the editor, its document workspace, rendering workflows and web/desktop delivery.

JavaScriptIndexedDBCloudflareNeutralinojsPlaywright

Inside the build

  • Folders, tabs, IndexedDB autosave and encrypted workspace storage.
  • Rich rendering, anchored review comments and optional Live Share.

Project notes

Actively maintained

Markdown Viewer is part of my work in developer tools · open source. The links above provide the available project and implementation details.

Next projectSei-Sangeet-Bangla

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +
+
+

Developer tools · Open source

Actively maintained
+

Markdown Viewer

+

A home for your Markdown.
And everything it can become.

+

Write, organize, and preview technical documents in one local-first workspace. Bring diagrams, equations, 3D models, and review conversations into the same flow.

+ +
My role
Creator & maintainer
Period
2024–ongoing
Built with
  • JavaScript
  • IndexedDB
  • Cloudflare
+
+
Markdown Viewer split view with LaTeX source, the quadratic formula, binomial theorem and calculus in the preview.
Equations, in context

LaTeX source and live MathJax output in the same workspace.

+
+

01 THE PROJECT

One document. A complete workflow.

+

It started with a simple idea: make Markdown easier to read, edit, and share.

Technical writing quickly outgrows a single preview pane. A README needs code examples. Research notes need equations. An architecture proposal needs diagrams and feedback. Moving between separate tools interrupts that work.

I created Markdown Viewer and maintain its editor, document storage, rendering pipeline, and web and desktop delivery. Community feedback and contributions have helped shape it into an open-source workspace for that whole process.

+
+

02 THE WORKSPACE

Pick up where you left off.

+

A document explorer keeps files and folders together. Tabs, independent scroll positions, and Editor, Split, and Preview modes let the workspace follow the task.

+
Markdown Viewer showing its document Explorer, Workspace and locked Secret Workspace alongside the Quick start screen.
A place for every document

Folders, open tabs, and a clear starting point for the next draft.

+
+

Organize without friction

Nested folders, favorites, recent files, search, and bulk actions. Import a GitHub repository while preserving its Markdown directory structure.

+

Save each document independently

Browser documents use IndexedDB; desktop documents live as ordinary Markdown files in a durable vault. Content loads when a document opens.

+

Build recovery into the workflow

Recover deleted files from a 30-day Trash, preserve conflicting edits as copies, and export a folder-preserving workspace ZIP.

+

Keep a private local space

Secret Workspace encrypts file contents and folder names with a password-derived AES-GCM key. The unlocked key stays in memory.

+
+
+

03 BEYOND PLAIN TEXT

Write the source. See the idea.

+

GitHub-style Markdown is the foundation. Rich code fences turn technical material into diagrams, charts, maps, models, and music, right beside the source.

+ +
More from the rendering toolkit
+

Also supported PlantUML, Graphviz/DOT, Vega-Lite, GeoJSON, and TopoJSON, alongside MathJax for LaTeX.

Mermaid, math, maps, STL, ABC, and Markmap render in the client after their libraries load. PlantUML, D2, Graphviz, Vega-Lite, and WaveDrom use remote services; map tiles and some diagram previews also use the network.

+
+

04 REVIEW & SHARING

From your draft to a shared conversation.

+

Feedback belongs next to the content it discusses. Anchored comments and threaded replies keep review attached to text, images, diagrams, and equations while leaving the Markdown source intact.

+

SHARE SNAPSHOT

Send a point-in-time copy.

Choose View only or Can edit. Small documents are compressed into the link; larger snapshots are stored in Cloudflare KV for 90 days. Recipients edit their own copy.

Snapshot details

LIVE SHARE

Work on the same document.

Yjs synchronizes edits, cursors, and review threads through a Cloudflare Durable Object. Host, editor, and viewer capabilities are checked by the relay; viewers can still comment.

Collaboration details
+

Snapshot and live invitations grant access to anyone holding the link. Live Share is not end-to-end encrypted. The relay does not persist document or comment content, but it does retain role capability metadata. Comments stay out of exports and snapshot links.

+
+

05 ENGINEERING CHOICES

More capability. A focused core.

+

The application stays mostly vanilla JavaScript. The work is in how storage, parsing, rendering, and export fit together.

+
  1. Markdown sourceA plain-text editor
  2. Parse & sanitizeMarked + DOMPurify
  3. Preview & exportRich-content preparation
+
01

Keep typing responsive

Size-aware debouncing, optional Web Worker parsing for large documents, cached blocks, and selective DOM updates reduce repeated work. Heavy renderer libraries load when needed.

02

Make the exported document complete

PDF and PNG use an off-screen snapshot that waits for fonts, images, math, and visual renderers. Markdown and standalone HTML offer other ways out, with remembered appearance choices for visual exports.

03

Share a core across web and desktop

A Neutralinojs wrapper reuses the web application, adds native file dialogs and a durable vault, and bundles renderer libraries. The web app also supports PWA installation and static or Docker hosting.

04

Check the workflows that matter

The repository includes Playwright coverage for editing, rich rendering, import/export, storage, sharing, keyboard interaction, and responsive layouts, plus Chromium, Firefox, and WebKit smoke tests. External integrations are mocked in the local suite.

+
+

06 BUILT WITH

The tools behind the workspace.

+

JavaScript

Application core

Editor behavior, preview orchestration, and browser APIs.

IndexedDB

Local persistence

Independent document records, metadata, and encrypted storage.

Cloudflare

Optional connected features

Pages hosting, KV snapshots, and Durable Object live rooms.

Neutralinojs

Desktop delivery

A shared web core with native dialogs and a durable file vault.

Docker

Self-hosting

An Nginx container for serving the static web application.

Playwright

Workflow verification

Browser automation for document, rendering, and interaction flows.

+
+

07 PROJECT NOTES

Built in public. Still moving forward.

+

Markdown Viewer has grown from a small preview tool into a document workspace through ongoing iteration and community contributions. The recent 3.10.2 release focuses on recovery, rendering correctness, review stability, and complete visual exports.

+

Its local-first approach keeps everyday writing on the device. Connected features have explicit service boundaries, and offline behavior depends on cached or bundled assets. The documentation records those tradeoffs alongside current capabilities.

+ +

Project documentation reviewed September 14, 2026. Product screenshots supplied by the author, captured September 2, 2026. Markdown Viewer is licensed under Apache 2.0.

+
+

Make room for your next idea.

Open the workspace and start with a document.

Try Markdown Viewer
+
+ Next projectSei-Sangeet-Bangla +

Project screenshot

+

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/medical-chatbot.html b/work/medical-chatbot.html index 0daaaeb..2ba6ef9 100644 --- a/work/medical-chatbot.html +++ b/work/medical-chatbot.html @@ -32,8 +32,9 @@ + -

AI · Retrieval experiment

Llama 2 GGML Medical Chatbot

An experiment in answering questions from medical reference documents with a quantized language model.

Project
Research prototype
Period
2023–2024
Built with
Python, Llama 2, LangChain
Llama 2 medical document retrieval prototype interface

Overview

An experiment in answering questions from medical reference documents with a quantized language model.

Built the retrieval workflow, model integration and chatbot interface using Python and LangChain.

The problem

Explore document-grounded question answering using local model inference and retrieved source passages.

My contribution

Built the retrieval workflow, model integration and chatbot interface using Python and LangChain.

PythonLlama 2LangChainFAISSChainlit

Inside the build

  • Retrieve document passages using sentence-transformer embeddings and FAISS.
  • Pass retrieved context to a pretrained Llama 2 model and return source documents with the answer.

Project notes

Research prototype

This is a document-retrieval experiment, not a clinical system or a substitute for professional medical advice. It uses a pretrained model; no model-training or clinical-accuracy claim is made.

Next projectBody Language Detection

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

AI · Retrieval experiment

Llama 2 GGML Medical Chatbot

An experiment in answering questions from medical reference documents with a quantized language model.

Project
Research prototype
Period
2023–2024
Built with
  • Python
  • Llama 2
  • LangChain
Llama 2 medical document retrieval prototype interface

Overview

An experiment in answering questions from medical reference documents with a quantized language model.

Built the retrieval workflow, model integration and chatbot interface using Python and LangChain.

The problem

Explore document-grounded question answering using local model inference and retrieved source passages.

My contribution

Built the retrieval workflow, model integration and chatbot interface using Python and LangChain.

  • Python
  • Llama 2
  • LangChain
  • FAISS
  • Chainlit

Inside the build

  • Retrieve document passages using sentence-transformer embeddings and FAISS.
  • Pass retrieved context to a pretrained Llama 2 model and return source documents with the answer.

Project notes

Research prototype

This is a document-retrieval experiment, not a clinical system or a substitute for professional medical advice. It uses a pretrained model; no model-training or clinical-accuracy claim is made.

Next projectBody Language Detection

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/medichain.html b/work/medichain.html index d979da6..8ab4c02 100644 --- a/work/medichain.html +++ b/work/medichain.html @@ -32,8 +32,9 @@ + -

Full stack · Blockchain prototype

MediChain

Making a medicine's journey visible across a pharmaceutical supply chain.

Project
Team prototype
Period
2024–2025
Built with
JavaScript, Solidity, Ethereum
MediChain dashboard showing medicine stock and supply-chain records.

Overview

Making a medicine's journey visible across a pharmaceutical supply chain.

Led full-stack development in a four-person team, connecting the web interface with blockchain workflows.

The problem

Medicine registration, inventory and purchase history need a shared record across supply-chain roles.

My contribution

Led full-stack development in a four-person team, connecting the web interface with blockchain workflows.

JavaScriptSolidityEthereumWeb3.jsMetaMask

Inside the build

  • Track medicine batches, lifecycle states and purchase history.
  • Connect role-based web interfaces to Ethereum contracts.

Project notes

Team prototype

MediChain is part of my work in full stack · blockchain prototype. The links above provide the available project and implementation details.

Next projectAMS

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Full stack · Blockchain prototype

MediChain

Making a medicine's journey visible across a pharmaceutical supply chain.

Project
Team prototype
Period
2024–2025
Built with
  • JavaScript
  • Solidity
  • Ethereum
MediChain dashboard showing medicine stock and supply-chain records.

Overview

Making a medicine's journey visible across a pharmaceutical supply chain.

Led full-stack development in a four-person team, connecting the web interface with blockchain workflows.

The problem

Medicine registration, inventory and purchase history need a shared record across supply-chain roles.

My contribution

Led full-stack development in a four-person team, connecting the web interface with blockchain workflows.

  • JavaScript
  • Solidity
  • Ethereum
  • Web3.js
  • MetaMask

Inside the build

  • Track medicine batches, lifecycle states and purchase history.
  • Connect role-based web interfaces to Ethereum contracts.

Project notes

Team prototype

MediChain is part of my work in full stack · blockchain prototype. The links above provide the available project and implementation details.

Next projectAMS

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/news-scraper.html b/work/news-scraper.html index 6979673..2bf36cf 100644 --- a/work/news-scraper.html +++ b/work/news-scraper.html @@ -32,8 +32,9 @@ + -

Automation · Python

News Scraping Platform

A Django experiment for collecting news articles from multiple sources.

Project
Archived experiment
Period
2023–2024
Built with
Python, Django, Selenium
News Scraping Platform — replaceable project image
Project image placeholder

Overview

A Django experiment for collecting news articles from multiple sources.

Built scraping workflows with BeautifulSoup and Selenium, integrated into Django.

The problem

Repeated article collection can be automated into a consistent data workflow.

My contribution

Built scraping workflows with BeautifulSoup and Selenium, integrated into Django.

PythonDjangoSeleniumBeautifulSoupMySQL

Inside the build

  • Concurrent collection from multiple publications.
  • Export collected records to Excel or MySQL.

Project notes

Archived experiment

News Scraping Platform is part of my work in automation · python. The links above provide the available project and implementation details.

Next projectTaskFlow

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Automation · Python

News Scraping Platform

A Django experiment for collecting news articles from multiple sources.

Project
Archived experiment
Period
2023–2024
Built with
  • Python
  • Django
  • Selenium
News Scraping Platform — replaceable project image
Project image placeholder

Overview

A Django experiment for collecting news articles from multiple sources.

Built scraping workflows with BeautifulSoup and Selenium, integrated into Django.

The problem

Repeated article collection can be automated into a consistent data workflow.

My contribution

Built scraping workflows with BeautifulSoup and Selenium, integrated into Django.

  • Python
  • Django
  • Selenium
  • BeautifulSoup
  • MySQL

Inside the build

  • Concurrent collection from multiple publications.
  • Export collected records to Excel or MySQL.

Project notes

Archived experiment

News Scraping Platform is part of my work in automation · python. The links above provide the available project and implementation details.

Next projectTaskFlow

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/notemarker.html b/work/notemarker.html index 7dabe41..12c6761 100644 --- a/work/notemarker.html +++ b/work/notemarker.html @@ -32,8 +32,9 @@ + -

Browser extension · Productivity

NoteMarker Extension

Keep the useful parts of the web, right where you found them.

Project
Released for Firefox & Chromium
Period
2024
Built with
JavaScript, WebExtensions, Browser Storage
NoteMarker highlights and sticky notes on a research paper webpage.

Overview

Keep the useful parts of the web, right where you found them.

Built and maintain an open-source extension for highlighting webpages and creating persistent notes.

The problem

Research gets fragmented when highlights and notes are separated from the pages that inspired them.

My contribution

Built and maintain an open-source extension for highlighting webpages and creating persistent notes.

JavaScriptWebExtensionsBrowser StorageHTML & CSS

Inside the build

  • Persistent five-color highlights and rich-text sticky notes.
  • Move, resize and manage annotations on the original webpage.

Project notes

Released for Firefox & Chromium

NoteMarker Extension is part of my work in browser extension · productivity. The links above provide the available project and implementation details.

Next projectSketchFlow

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Browser extension · Productivity

NoteMarker Extension

Keep the useful parts of the web, right where you found them.

Project
Released for Firefox & Chromium
Period
2024
Built with
  • JavaScript
  • WebExtensions
  • Browser Storage
NoteMarker highlights and sticky notes on a research paper webpage.

Overview

Keep the useful parts of the web, right where you found them.

Built and maintain an open-source extension for highlighting webpages and creating persistent notes.

The problem

Research gets fragmented when highlights and notes are separated from the pages that inspired them.

My contribution

Built and maintain an open-source extension for highlighting webpages and creating persistent notes.

  • JavaScript
  • WebExtensions
  • Browser Storage
  • HTML & CSS

Inside the build

  • Persistent five-color highlights and rich-text sticky notes.
  • Move, resize and manage annotations on the original webpage.

Project notes

Released for Firefox & Chromium

NoteMarker Extension is part of my work in browser extension · productivity. The links above provide the available project and implementation details.

Next projectSketchFlow

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/sei-sangeet-bangla.html b/work/sei-sangeet-bangla.html index 5e076b3..ac72eca 100644 --- a/work/sei-sangeet-bangla.html +++ b/work/sei-sangeet-bangla.html @@ -32,8 +32,9 @@ + -

Web app · Music

Sei-Sangeet-Bangla

A Bengali music experience set inside an illustrated room, with a song list and a familiar player.

Project
Private source
Period
2026
Built with
Web development
Sei-Sangeet-Bangla music room with Bengali song list and player

Overview

A Bengali music experience set inside an illustrated room, with a song list and a familiar player.

Developed Sei-Sangeet-Bangla as a personal music project.

The problem

Create a dedicated web experience around Bengali music.

My contribution

Developed Sei-Sangeet-Bangla as a personal music project.

Web development

Inside the build

  • Browse the song list inside a nostalgic, illustrated Bengali room.
  • Use track, playback, seek and volume controls in the music player.

Project notes

Private source

The project is included in my portfolio, while its source repository remains private.

Next projectLlama 2 GGML Medical Chatbot

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Web app · Music

Sei-Sangeet-Bangla

A Bengali music experience set inside an illustrated room, with a song list and a familiar player.

Project
Private source
Period
2026
Built with
  • Web development
Sei-Sangeet-Bangla music room with Bengali song list and player

Overview

A Bengali music experience set inside an illustrated room, with a song list and a familiar player.

Developed Sei-Sangeet-Bangla as a personal music project.

The problem

Create a dedicated web experience around Bengali music.

My contribution

Developed Sei-Sangeet-Bangla as a personal music project.

  • Web development

Inside the build

  • Browse the song list inside a nostalgic, illustrated Bengali room.
  • Use track, playback, seek and volume controls in the music player.

Project notes

Private source

The project is included in my portfolio, while its source repository remains private.

Next projectLlama 2 GGML Medical Chatbot

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/simon.html b/work/simon.html index be6fa00..3aee11d 100644 --- a/work/simon.html +++ b/work/simon.html @@ -32,8 +32,9 @@ + -

Web app · Memory game

Simon

A browser memory game built around color, sound and an increasingly long sequence to remember.

Project
Open-source game
Period
2024
Built with
JavaScript, HTML & CSS, localStorage
Simon — replaceable project image
Project image placeholder

Overview

A browser memory game built around color, sound and an increasingly long sequence to remember.

Built the game interface, randomized sequence logic, audio feedback and local high-score tracking in JavaScript.

The problem

Recreate the classic Simon memory challenge with immediate visual feedback and simple browser controls.

My contribution

Built the game interface, randomized sequence logic, audio feedback and local high-score tracking in JavaScript.

JavaScriptHTML & CSSlocalStorage

Inside the build

  • Follow flashing colors and repeat the growing sequence to advance through levels.
  • Hear feedback on button presses and mistakes, restart a round and keep the highest score in the current browser.

Project notes

Open-source game

The high score is saved locally in the current browser. The game is a personal implementation of the classic Simon memory challenge.

Next projectMarkdown Viewer

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Web app · Memory game

Simon

A browser memory game built around color, sound and an increasingly long sequence to remember.

Project
Open-source game
Period
2024
Built with
  • JavaScript
  • HTML & CSS
  • localStorage
Simon — replaceable project image
Project image placeholder

Overview

A browser memory game built around color, sound and an increasingly long sequence to remember.

Built the game interface, randomized sequence logic, audio feedback and local high-score tracking in JavaScript.

The problem

Recreate the classic Simon memory challenge with immediate visual feedback and simple browser controls.

My contribution

Built the game interface, randomized sequence logic, audio feedback and local high-score tracking in JavaScript.

  • JavaScript
  • HTML & CSS
  • localStorage

Inside the build

  • Follow flashing colors and repeat the growing sequence to advance through levels.
  • Hear feedback on button presses and mistakes, restart a round and keep the highest score in the current browser.

Project notes

Open-source game

The high score is saved locally in the current browser. The game is a personal implementation of the classic Simon memory challenge.

Next projectMarkdown Viewer

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/sketchflow.html b/work/sketchflow.html index aa7b5d5..38628e2 100644 --- a/work/sketchflow.html +++ b/work/sketchflow.html @@ -32,8 +32,9 @@ + -

Canvas · Creative tool

SketchFlow

A browser canvas for sketching ideas, diagrams and freehand drawings.

Project
Open-source experiment
Period
2024–2026
Built with
JavaScript, Canvas API, HTML & CSS
SketchFlow drawing canvas with a colorful circular sketch.

Overview

A browser canvas for sketching ideas, diagrams and freehand drawings.

Built the canvas interaction, shape tools and drawing controls in vanilla JavaScript.

The problem

Quick visual thinking benefits from a lightweight drawing workspace.

My contribution

Built the canvas interaction, shape tools and drawing controls in vanilla JavaScript.

JavaScriptCanvas APIHTML & CSS

Inside the build

  • Draw shapes, arrows, text and freehand strokes.
  • Adjust styling and zoom; export PNG or save JSON.

Project notes

Open-source experiment

SketchFlow is part of my work in canvas · creative tool. The links above provide the available project and implementation details.

Next projectLlama 2 GGML CSV Chatbot

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Canvas · Creative tool

SketchFlow

A browser canvas for sketching ideas, diagrams and freehand drawings.

Project
Open-source experiment
Period
2024–2026
Built with
  • JavaScript
  • Canvas API
  • HTML & CSS
SketchFlow drawing canvas with a colorful circular sketch.

Overview

A browser canvas for sketching ideas, diagrams and freehand drawings.

Built the canvas interaction, shape tools and drawing controls in vanilla JavaScript.

The problem

Quick visual thinking benefits from a lightweight drawing workspace.

My contribution

Built the canvas interaction, shape tools and drawing controls in vanilla JavaScript.

  • JavaScript
  • Canvas API
  • HTML & CSS

Inside the build

  • Draw shapes, arrows, text and freehand strokes.
  • Adjust styling and zoom; export PNG or save JSON.

Project notes

Open-source experiment

SketchFlow is part of my work in canvas · creative tool. The links above provide the available project and implementation details.

Next projectLlama 2 GGML CSV Chatbot

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file diff --git a/work/taskflow.html b/work/taskflow.html index a383714..d5823ee 100644 --- a/work/taskflow.html +++ b/work/taskflow.html @@ -32,8 +32,9 @@ + -

Developer tools · Productivity

TaskFlow

A flexible task board for organizing work, one column and one card at a time.

Project
Open-source tool
Period
2024–2025
Built with
JavaScript, HTML & CSS, Bootstrap
TaskFlow task cards arranged in To Do, In Progress and Done columns

Overview

A flexible task board for organizing work, one column and one card at a time.

Built a browser-based task management interface with customizable columns and drag-and-drop task cards.

The problem

Keep tasks, priorities and supporting details together in a lightweight visual workflow.

My contribution

Built a browser-based task management interface with customizable columns and drag-and-drop task cards.

JavaScriptHTML & CSSBootstraplocalStorage

Inside the build

  • Organize tasks with due dates, priorities, assignees, tags, comments and checklists.
  • Search tasks, move cards between columns and save the board in browser localStorage.

Project notes

Open-source tool

The board is stored in the current browser. Assignee fields help organize work; they do not imply a hosted multi-user collaboration backend.

Next projectSimon

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file +

Developer tools · Productivity

TaskFlow

A flexible task board for organizing work, one column and one card at a time.

Project
Open-source tool
Period
2024–2025
Built with
  • JavaScript
  • HTML & CSS
  • Bootstrap
TaskFlow task cards arranged in To Do, In Progress and Done columns

Overview

A flexible task board for organizing work, one column and one card at a time.

Built a browser-based task management interface with customizable columns and drag-and-drop task cards.

The problem

Keep tasks, priorities and supporting details together in a lightweight visual workflow.

My contribution

Built a browser-based task management interface with customizable columns and drag-and-drop task cards.

  • JavaScript
  • HTML & CSS
  • Bootstrap
  • localStorage

Inside the build

  • Organize tasks with due dates, priorities, assignees, tags, comments and checklists.
  • Search tasks, move cards between columns and save the board in browser localStorage.

Project notes

Open-source tool

The board is stored in the current browser. Assignee fields help organize work; they do not imply a hosted multi-user collaboration backend.

Next projectSimon

An idea, an opportunity, or just a hello?

Build with me.

Bug Run

A tiny break between builds.

Score0
Best0
An optional jumping game. The rest of this portfolio works without JavaScript.

Space or tap to start. Dodge a few bugs.

\ No newline at end of file