# Personal Portfolio

JS-rendered personal profile card (J002 layout): avatar, social links, experience stats, contact buttons, Portfolio grid with tag filter and Skills year rings.

## Overview

| Item | Value |
|---|---|
| Created | 2023-02-26 |
| Category | Personal Examples |
| HTML files | `index.html` |
| Source | [target/20230226](https://github.com/pardnio/demo-web/tree/main/target/20230226) |

## Live Preview

<iframe src="/target/20230226/" title="Personal Portfolio" loading="lazy" style="width:100%;height:640px;border:1px solid #e5e7eb;border-radius:8px"></iframe>

[Open the example in a new tab](/target/20230226/)

## Page Sections

| Section | Description |
|---|---|
| Top user card | setTopUser(): avatar (or name initial), name with title tooltip, LinkedIn / Instagram / Facebook / GitHub / blog links (nulls skipped), 3 experience stats, '查看原碼' main button and contact icons (email, Messenger). |
| Page tabs | ul#page-tab with Portfolio / Skills, selected state via sel attribute. |
| Portfolio (default, ?page=portfolio) | setPortfolio(): 20 sample figures with image, title, 開啟 link and 分享 button; floating ul#portfolio-tab filters ALL / tag 1 / tag 2. |
| Skills (?page=skills) | setSkills(): skill groups (iOS, Frontend, Backend, Database, Google Cloud Platform, OS, more) with per-item year rings, distributed into two masonry-like columns by comparing scrollHeight. |

## Interactions

- All DOM built on DOMContentLoaded from webConfig with PDRenderKit helpers ("tag#id.class"._(), _child, _click, _go, _open, $timestamp.$gone for years since a date).
- #body-container scroll &gt;= 40px adds 'show' to #portfolio-tab.
- Tabs on narrow screens (page tab &lt; 640px, portfolio tab &lt; 800px): first tap adds 'expand', second tap selects and re-renders the list.
- copy(url): temporary readonly input + document.execCommand('copy') then alert('已拷貝至剪貼簿.').
- _LazyListener() (PDRenderKit) is started for image lazy loading.

## Dependencies

| Name | Version | Source |
|---|---|---|
| PDRenderKit | 1.0.0 (script tag; preload tag requests 1.0.2) | https://cdn.jsdelivr.net/npm/pdrenderkit@1.0.0/dist/PDRenderKit.js |
| Font Awesome Free | 6.5.2 | https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.2/css/all.min.css |
| Google Analytics gtag | - | https://www.googletagmanager.com/gtag/js?id=G-L5VYEZPVXX |
| js/index.js (local app code with webConfig sample data) | - | js/index.js |

## Styling

| Item | Value |
|---|---|
| SCSS entry | `sass/index.scss` |
| Output CSS | `css/index.css` |
| Partials | `J002/_nav.scss`, `J002/_div.scss`, `J002/_portfolio-tab.scss`, `J002/_portfolio-list.scss`, `J002/_skill-list.scss`, `J002/_top-user.scss`, `J002/top-user/_head.scss`, `J002/top-user/_name.scss`, `J002/top-user/_link.scss`, `J002/top-user/_exp.scss`, `J002/top-user/_button.scss`, `J002/top-user/_tab.scss` |

Layout namespaced under J002; top-user split into head / name / link / exp / button / tab partials. All colours, sizes and gaps come from CSS variables declared in index.html.

```bash
cd target/20230226
npx sass sass/index.scss css/index.css --no-source-map || exit 1
```

## Techniques

- Extensive CSS-variable theming in inline &lt;style&gt;: --global-bg, --user-top-*, --portfolio-*, --skill-* (gradients, sizes, gaps, ratio).
- Skill year rings: Sass @for 1..10 generates [year="N"] rules rotating two half-circle layers by 36deg per year.
- content: attr(num) / attr(title) for stats and tooltips; [num="N"] and [sel="N"] attribute selectors generated with @for.
- Mobile-first breakpoints 320 / 480 / 640 / 800 / 1024px and @media (hover: hover).

## External Assets

- https://pardn.io/web-template/target/20230226/image/bg.jpg (--global-bg, absolute URL)
- `https://fakeimg.pl/1024/282828/eae0d0/?text=sample`
- `https://pardn.io/image/head.png`
- `https://pardn.io/web-template/image/20230226.jpg`

## Known Issues

- --global-bg is hard-coded to https://pardn.io/web-template/target/20230226/image/bg.jpg, so the copied page still loads the background from pardn.io instead of its own image/bg.jpg; it breaks if that remote file is unavailable.

## Related

- [Project Structure](/project-structure)
- [Styling Workflow](/styling-workflow)
- [Frameworks](/frameworks)
