Update Print Portfolio

This commit is contained in:
Khoa Vo 2026-04-21 07:52:06 +07:00
parent e41d8ec3cb
commit faefc32c49
89 changed files with 887 additions and 788 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

135
dist/assets/index-CDV1Fqci.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/index-DS6IUas1.css vendored Normal file

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View file

@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vndk-icon.svg" /> <link rel="icon" type="image/svg+xml" href="/vndk-icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Khoa Vo - AI Creative Lead</title> <title>Khoa Vo - AI Creative Lead</title>
<script type="module" crossorigin src="/assets/index-C5_7qcB8.js"></script> <script type="module" crossorigin src="/assets/index-CDV1Fqci.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BOGP2GsU.css"> <link rel="stylesheet" crossorigin href="/assets/index-DS6IUas1.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

0
node_modules/.bin/autoprefixer generated vendored Normal file → Executable file
View file

0
node_modules/.bin/autoprefixer.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/autoprefixer.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/baseline-browser-mapping generated vendored Normal file → Executable file
View file

0
node_modules/.bin/baseline-browser-mapping.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/baseline-browser-mapping.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/browserslist generated vendored Normal file → Executable file
View file

0
node_modules/.bin/browserslist.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/browserslist.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/cssesc generated vendored Normal file → Executable file
View file

0
node_modules/.bin/cssesc.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/cssesc.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/esbuild generated vendored Normal file → Executable file
View file

0
node_modules/.bin/esbuild.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/esbuild.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/jiti generated vendored Normal file → Executable file
View file

0
node_modules/.bin/jiti.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/jiti.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/jsesc generated vendored Normal file → Executable file
View file

0
node_modules/.bin/jsesc.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/jsesc.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/json5 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/json5.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/json5.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/loose-envify generated vendored Normal file → Executable file
View file

0
node_modules/.bin/loose-envify.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/loose-envify.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/nanoid generated vendored Normal file → Executable file
View file

0
node_modules/.bin/nanoid.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/nanoid.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/parser generated vendored Normal file → Executable file
View file

0
node_modules/.bin/parser.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/parser.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/resolve generated vendored Normal file → Executable file
View file

0
node_modules/.bin/resolve.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/resolve.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/rollup generated vendored Normal file → Executable file
View file

0
node_modules/.bin/rollup.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/rollup.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/semver generated vendored Normal file → Executable file
View file

0
node_modules/.bin/semver.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/semver.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/sucrase generated vendored Normal file → Executable file
View file

0
node_modules/.bin/sucrase-node generated vendored Normal file → Executable file
View file

0
node_modules/.bin/sucrase-node.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/sucrase-node.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/sucrase.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/sucrase.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/tailwind generated vendored Normal file → Executable file
View file

0
node_modules/.bin/tailwind.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/tailwind.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/tailwindcss generated vendored Normal file → Executable file
View file

0
node_modules/.bin/tailwindcss.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/tailwindcss.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/update-browserslist-db generated vendored Normal file → Executable file
View file

0
node_modules/.bin/update-browserslist-db.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/update-browserslist-db.ps1 generated vendored Normal file → Executable file
View file

0
node_modules/.bin/vite generated vendored Normal file → Executable file
View file

0
node_modules/.bin/vite.cmd generated vendored Normal file → Executable file
View file

0
node_modules/.bin/vite.ps1 generated vendored Normal file → Executable file
View file

59
node_modules/.package-lock.json generated vendored
View file

@ -48,7 +48,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@ -300,18 +299,18 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12", "version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [ "cpu": [
"x64" "arm64"
], ],
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "darwin"
], ],
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@ -412,32 +411,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rollup/rollup-win32-x64-gnu": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.60.1", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
"integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
"cpu": [ "cpu": [
"x64" "arm64"
], ],
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "darwin"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
"integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
] ]
}, },
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
@ -505,7 +490,6 @@
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
@ -666,7 +650,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.10.12", "baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782", "caniuse-lite": "^1.0.30001782",
@ -972,6 +955,21 @@
} }
} }
}, },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -1086,7 +1084,6 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@ -1355,7 +1352,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@ -1525,7 +1521,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@ -1538,7 +1533,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@ -1848,7 +1842,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -1926,7 +1919,6 @@
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.4",
@ -2020,7 +2012,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },

View file

@ -1,50 +1,50 @@
{ {
"hash": "90ee4654", "hash": "990005cb",
"configHash": "fbb0cfde", "configHash": "dd418e0b",
"lockfileHash": "a6533fbd", "lockfileHash": "9e20975c",
"browserHash": "8360e944", "browserHash": "123a9614",
"optimized": { "optimized": {
"react": { "react": {
"src": "../../react/index.js", "src": "../../react/index.js",
"file": "react.js", "file": "react.js",
"fileHash": "ecc3583d", "fileHash": "2e48ac20",
"needsInterop": true "needsInterop": true
}, },
"react-dom": { "react-dom": {
"src": "../../react-dom/index.js", "src": "../../react-dom/index.js",
"file": "react-dom.js", "file": "react-dom.js",
"fileHash": "5d314a57", "fileHash": "455f0ae0",
"needsInterop": true "needsInterop": true
}, },
"react/jsx-dev-runtime": { "react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js", "src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js", "file": "react_jsx-dev-runtime.js",
"fileHash": "0de83b10", "fileHash": "82ad24c4",
"needsInterop": true "needsInterop": true
}, },
"react/jsx-runtime": { "react/jsx-runtime": {
"src": "../../react/jsx-runtime.js", "src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js", "file": "react_jsx-runtime.js",
"fileHash": "5ce06852", "fileHash": "67186fcc",
"needsInterop": true
},
"lucide-react": {
"src": "../../lucide-react/dist/esm/lucide-react.js",
"file": "lucide-react.js",
"fileHash": "5c75eae5",
"needsInterop": false
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "c3cad18b",
"needsInterop": true "needsInterop": true
}, },
"framer-motion": { "framer-motion": {
"src": "../../framer-motion/dist/es/index.mjs", "src": "../../framer-motion/dist/es/index.mjs",
"file": "framer-motion.js", "file": "framer-motion.js",
"fileHash": "53e35944", "fileHash": "f64f8e8e",
"needsInterop": false "needsInterop": false
},
"lucide-react": {
"src": "../../lucide-react/dist/esm/lucide-react.js",
"file": "lucide-react.js",
"fileHash": "36896c9b",
"needsInterop": false
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "95365ffd",
"needsInterop": true
} }
}, },
"chunks": { "chunks": {

3
node_modules/@esbuild/darwin-arm64/README.md generated vendored Normal file
View file

@ -0,0 +1,3 @@
# esbuild
This is the macOS ARM 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.

Binary file not shown.

View file

@ -1,7 +1,7 @@
{ {
"name": "@esbuild/win32-x64", "name": "@esbuild/darwin-arm64",
"version": "0.25.12", "version": "0.25.12",
"description": "The Windows 64-bit binary for esbuild, a JavaScript bundler.", "description": "The macOS ARM 64-bit binary for esbuild, a JavaScript bundler.",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/evanw/esbuild.git" "url": "git+https://github.com/evanw/esbuild.git"
@ -12,9 +12,9 @@
"node": ">=18" "node": ">=18"
}, },
"os": [ "os": [
"win32" "darwin"
], ],
"cpu": [ "cpu": [
"x64" "arm64"
] ]
} }

View file

@ -1,3 +0,0 @@
# esbuild
This is the Windows 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.

3
node_modules/@rollup/rollup-darwin-arm64/README.md generated vendored Normal file
View file

@ -0,0 +1,3 @@
# `@rollup/rollup-darwin-arm64`
This is the **aarch64-apple-darwin** binary for `rollup`

View file

@ -1,14 +1,14 @@
{ {
"name": "@rollup/rollup-win32-x64-gnu", "name": "@rollup/rollup-darwin-arm64",
"version": "4.60.1", "version": "4.60.1",
"os": [ "os": [
"win32" "darwin"
], ],
"cpu": [ "cpu": [
"x64" "arm64"
], ],
"files": [ "files": [
"rollup.win32-x64-gnu.node" "rollup.darwin-arm64.node"
], ],
"description": "Native bindings for Rollup", "description": "Native bindings for Rollup",
"author": "Lukas Taegert-Atkinson", "author": "Lukas Taegert-Atkinson",
@ -18,5 +18,5 @@
"type": "git", "type": "git",
"url": "git+https://github.com/rollup/rollup.git" "url": "git+https://github.com/rollup/rollup.git"
}, },
"main": "./rollup.win32-x64-gnu.node" "main": "./rollup.darwin-arm64.node"
} }

Binary file not shown.

View file

@ -1,3 +0,0 @@
# `@rollup/rollup-win32-x64-gnu`
This is the **x86_64-pc-windows-gnu** binary for `rollup`

View file

@ -1,3 +0,0 @@
# `@rollup/rollup-win32-x64-msvc`
This is the **x86_64-pc-windows-msvc** binary for `rollup`

View file

@ -1,22 +0,0 @@
{
"name": "@rollup/rollup-win32-x64-msvc",
"version": "4.60.1",
"os": [
"win32"
],
"cpu": [
"x64"
],
"files": [
"rollup.win32-x64-msvc.node"
],
"description": "Native bindings for Rollup",
"author": "Lukas Taegert-Atkinson",
"homepage": "https://rollupjs.org/",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/rollup/rollup.git"
},
"main": "./rollup.win32-x64-msvc.node"
}

22
node_modules/fsevents/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
MIT License
-----------
Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

89
node_modules/fsevents/README.md generated vendored Normal file
View file

@ -0,0 +1,89 @@
# fsevents
Native access to MacOS FSEvents in [Node.js](https://nodejs.org/)
The FSEvents API in MacOS allows applications to register for notifications of
changes to a given directory tree. It is a very fast and lightweight alternative
to kqueue.
This is a low-level library. For a cross-platform file watching module that
uses fsevents, check out [Chokidar](https://github.com/paulmillr/chokidar).
## Usage
```sh
npm install fsevents
```
Supports only **Node.js v8.16 and higher**.
```js
const fsevents = require('fsevents');
// To start observation
const stop = fsevents.watch(__dirname, (path, flags, id) => {
const info = fsevents.getInfo(path, flags);
});
// To end observation
stop();
```
> **Important note:** The API behaviour is slightly different from typical JS APIs. The `stop` function **must** be
> retrieved and stored somewhere, even if you don't plan to stop the watcher. If you forget it, the garbage collector
> will eventually kick in, the watcher will be unregistered, and your callbacks won't be called anymore.
The callback passed as the second parameter to `.watch` get's called whenever the operating system detects a
a change in the file system. It takes three arguments:
###### `fsevents.watch(dirname: string, (path: string, flags: number, id: string) => void): () => Promise<undefined>`
* `path: string` - the item in the filesystem that have been changed
* `flags: number` - a numeric value describing what the change was
* `id: string` - an unique-id identifying this specific event
Returns closer callback which when called returns a Promise resolving when the watcher process has been shut down.
###### `fsevents.getInfo(path: string, flags: number, id: string): FsEventInfo`
The `getInfo` function takes the `path`, `flags` and `id` arguments and converts those parameters into a structure
that is easier to digest to determine what the change was.
The `FsEventsInfo` has the following shape:
```js
/**
* @typedef {'created'|'modified'|'deleted'|'moved'|'root-changed'|'cloned'|'unknown'} FsEventsEvent
* @typedef {'file'|'directory'|'symlink'} FsEventsType
*/
{
"event": "created", // {FsEventsEvent}
"path": "file.txt",
"type": "file", // {FsEventsType}
"changes": {
"inode": true, // Had iNode Meta-Information changed
"finder": false, // Had Finder Meta-Data changed
"access": false, // Had access permissions changed
"xattrs": false // Had xAttributes changed
},
"flags": 0x100000000
}
```
## Changelog
- v2.3 supports Apple Silicon ARM CPUs
- v2 supports node 8.16+ and reduces package size massively
- v1.2.8 supports node 6+
- v1.2.7 supports node 4+
## Troubleshooting
- I'm getting `EBADPLATFORM` `Unsupported platform for fsevents` error.
- It's fine, nothing is broken. fsevents is macos-only. Other platforms are skipped. If you want to hide this warning, report a bug to NPM bugtracker asking them to hide ebadplatform warnings by default.
## License
The MIT License Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller — see LICENSE file.
Visit our [GitHub page](https://github.com/fsevents/fsevents) and [NPM Page](https://npmjs.org/package/fsevents)

46
node_modules/fsevents/fsevents.d.ts generated vendored Normal file
View file

@ -0,0 +1,46 @@
declare type Event = "created" | "cloned" | "modified" | "deleted" | "moved" | "root-changed" | "unknown";
declare type Type = "file" | "directory" | "symlink";
declare type FileChanges = {
inode: boolean;
finder: boolean;
access: boolean;
xattrs: boolean;
};
declare type Info = {
event: Event;
path: string;
type: Type;
changes: FileChanges;
flags: number;
};
declare type WatchHandler = (path: string, flags: number, id: string) => void;
export declare function watch(path: string, handler: WatchHandler): () => Promise<void>;
export declare function watch(path: string, since: number, handler: WatchHandler): () => Promise<void>;
export declare function getInfo(path: string, flags: number): Info;
export declare const constants: {
None: 0x00000000;
MustScanSubDirs: 0x00000001;
UserDropped: 0x00000002;
KernelDropped: 0x00000004;
EventIdsWrapped: 0x00000008;
HistoryDone: 0x00000010;
RootChanged: 0x00000020;
Mount: 0x00000040;
Unmount: 0x00000080;
ItemCreated: 0x00000100;
ItemRemoved: 0x00000200;
ItemInodeMetaMod: 0x00000400;
ItemRenamed: 0x00000800;
ItemModified: 0x00001000;
ItemFinderInfoMod: 0x00002000;
ItemChangeOwner: 0x00004000;
ItemXattrMod: 0x00008000;
ItemIsFile: 0x00010000;
ItemIsDir: 0x00020000;
ItemIsSymlink: 0x00040000;
ItemIsHardlink: 0x00100000;
ItemIsLastHardlink: 0x00200000;
OwnEvent: 0x00080000;
ItemCloned: 0x00400000;
};
export {};

83
node_modules/fsevents/fsevents.js generated vendored Normal file
View file

@ -0,0 +1,83 @@
/*
** © 2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
** Licensed under MIT License.
*/
/* jshint node:true */
"use strict";
if (process.platform !== "darwin") {
throw new Error(`Module 'fsevents' is not compatible with platform '${process.platform}'`);
}
const Native = require("./fsevents.node");
const events = Native.constants;
function watch(path, since, handler) {
if (typeof path !== "string") {
throw new TypeError(`fsevents argument 1 must be a string and not a ${typeof path}`);
}
if ("function" === typeof since && "undefined" === typeof handler) {
handler = since;
since = Native.flags.SinceNow;
}
if (typeof since !== "number") {
throw new TypeError(`fsevents argument 2 must be a number and not a ${typeof since}`);
}
if (typeof handler !== "function") {
throw new TypeError(`fsevents argument 3 must be a function and not a ${typeof handler}`);
}
let instance = Native.start(Native.global, path, since, handler);
if (!instance) throw new Error(`could not watch: ${path}`);
return () => {
const result = instance ? Promise.resolve(instance).then(Native.stop) : Promise.resolve(undefined);
instance = undefined;
return result;
};
}
function getInfo(path, flags) {
return {
path,
flags,
event: getEventType(flags),
type: getFileType(flags),
changes: getFileChanges(flags),
};
}
function getFileType(flags) {
if (events.ItemIsFile & flags) return "file";
if (events.ItemIsDir & flags) return "directory";
if (events.MustScanSubDirs & flags) return "directory";
if (events.ItemIsSymlink & flags) return "symlink";
}
function anyIsTrue(obj) {
for (let key in obj) {
if (obj[key]) return true;
}
return false;
}
function getEventType(flags) {
if (events.ItemRemoved & flags) return "deleted";
if (events.ItemRenamed & flags) return "moved";
if (events.ItemCreated & flags) return "created";
if (events.ItemModified & flags) return "modified";
if (events.RootChanged & flags) return "root-changed";
if (events.ItemCloned & flags) return "cloned";
if (anyIsTrue(flags)) return "modified";
return "unknown";
}
function getFileChanges(flags) {
return {
inode: !!(events.ItemInodeMetaMod & flags),
finder: !!(events.ItemFinderInfoMod & flags),
access: !!(events.ItemChangeOwner & flags),
xattrs: !!(events.ItemXattrMod & flags),
};
}
exports.watch = watch;
exports.getInfo = getInfo;
exports.constants = events;

BIN
node_modules/fsevents/fsevents.node generated vendored Executable file

Binary file not shown.

62
node_modules/fsevents/package.json generated vendored Normal file
View file

@ -0,0 +1,62 @@
{
"name": "fsevents",
"version": "2.3.3",
"description": "Native Access to MacOS FSEvents",
"main": "fsevents.js",
"types": "fsevents.d.ts",
"os": [
"darwin"
],
"files": [
"fsevents.d.ts",
"fsevents.js",
"fsevents.node"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
},
"scripts": {
"clean": "node-gyp clean && rm -f fsevents.node",
"build": "node-gyp clean && rm -f fsevents.node && node-gyp rebuild && node-gyp clean",
"test": "/bin/bash ./test.sh 2>/dev/null",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "https://github.com/fsevents/fsevents.git"
},
"keywords": [
"fsevents",
"mac"
],
"contributors": [
{
"name": "Philipp Dunkel",
"email": "pip@pipobscure.com"
},
{
"name": "Ben Noordhuis",
"email": "info@bnoordhuis.nl"
},
{
"name": "Elan Shankar",
"email": "elan.shanker@gmail.com"
},
{
"name": "Miroslav Bajtoš",
"email": "mbajtoss@gmail.com"
},
{
"name": "Paul Miller",
"url": "https://paulmillr.com"
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/fsevents/fsevents/issues"
},
"homepage": "https://github.com/fsevents/fsevents",
"devDependencies": {
"node-gyp": "^9.4.0"
}
}

10
package-lock.json generated
View file

@ -67,7 +67,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@ -1271,7 +1270,6 @@
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
@ -1432,7 +1430,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.10.12", "baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782", "caniuse-lite": "^1.0.30001782",
@ -1867,7 +1864,6 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@ -2136,7 +2132,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@ -2306,7 +2301,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@ -2319,7 +2313,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@ -2629,7 +2622,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -2707,7 +2699,6 @@
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.4",
@ -2801,7 +2792,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },

View file

@ -22,7 +22,7 @@ const PERSONAL_INFO = {
// --- CREATIVE PERSONA DATA --- // --- CREATIVE PERSONA DATA ---
const CREATIVE_DATA = { const CREATIVE_DATA = {
title: "AI Creative Lead & Motion Designer", title: "Creative Manager & AI Innovation Lead",
summary: "Visionary Creative Leader with 9+ years of expertise bridging brand strategy, digital design, motion graphics, and cutting-edge generative AI. Currently pioneering AI-augmented creative workflows at Phibious, merging traditional art direction with ComfyUI, Stable Diffusion, and FLUX to redefine what's possible in visual storytelling. Previously led eCommerce design at P&G, shaping digital experiences for millions of consumers across Southeast Asia.", summary: "Visionary Creative Leader with 9+ years of expertise bridging brand strategy, digital design, motion graphics, and cutting-edge generative AI. Currently pioneering AI-augmented creative workflows at Phibious, merging traditional art direction with ComfyUI, Stable Diffusion, and FLUX to redefine what's possible in visual storytelling. Previously led eCommerce design at P&G, shaping digital experiences for millions of consumers across Southeast Asia.",
tagline: "Where Design Meets Intelligence", tagline: "Where Design Meets Intelligence",
skills: [ skills: [
@ -94,10 +94,10 @@ const CREATIVE_DATA = {
period: "2025 - Present", period: "2025 - Present",
location: "Ho Chi Minh City", location: "Ho Chi Minh City",
highlights: [ highlights: [
"Spearhead the integration of generative AI (ComfyUI, Stable Diffusion, FLUX) into creative workflows, reducing production time by 50%", "Spearheaded the transformation of video production workflows via AI and automation, achieving a 60% measurable gain in output volume",
"Lead cross-functional teams of designers, copywriters, and data analysts to deliver technology-driven creative campaigns", "Acted as a creative multiplier, leading regional stakeholders and cross-functional teams to automate end-to-end content lifecycles",
"Developed proprietary AI tools for mood boarding, concept visualization, and rapid prototyping", "Designed and deployed Agentic AI systems and custom frameworks for rapid concept-to-video prototyping, serving global Fortune 500 brands",
"Created AI-augmented design concepts for digital and physical platforms serving Fortune 500 clients" "Drove Regional Enablement by establishing SOPs and mentoring 20+ producers on prompt engineering, AI ethics, and workflow standardization"
] ]
}, },
{ {
@ -284,7 +284,7 @@ const LandingPage = ({ onSelect }) => {
transition={{ duration: 0.8, delay: 1 }} transition={{ duration: 0.8, delay: 1 }}
className="text-[10px] font-bold tracking-[0.4em] text-[#1A1A1A]/30 uppercase mb-10 md:mb-12" className="text-[10px] font-bold tracking-[0.4em] text-[#1A1A1A]/30 uppercase mb-10 md:mb-12"
> >
AI Creative Lead & Motion Designer Creative Manager & AI Innovation Lead
</motion.p> </motion.p>
{/* Enter Button (Desktop only) */} {/* Enter Button (Desktop only) */}

View file

@ -1,310 +1,418 @@
import React from 'react'; import React from 'react';
const PERSONAL_INFO = { const PRINT_PERSONAL_INFO = {
name: "VO NGUYEN DANG KHOA", name: "Khoa.vo",
title: "AI CREATIVE LEAD & SOFTWARE DEVELOPER", title: "CREATIVE MANAGER & AI INNOVATION LEAD",
dob: "19/01/1993", location: "Ho Chi Minh City, Vietnam",
nationality: "Vietnam",
marital: "Married",
gender: "Male",
phone: "0398300340", phone: "0398300340",
email: "vonguyendangkhoa@gmail.com", email: "vonguyendangkhoa@gmail.com",
location: "Ho Chi Minh City, Vietnam",
linkedin: "linkedin.com/in/khoavo", linkedin: "linkedin.com/in/khoavo",
portfolio: "khoavo.myds.me", portfolio: "khoavo.myds.me",
github: "git.khoavo.myds.me/vndangkhoa", summaryHeadline: "Visionary Creative Leader merging Brand Strategy with Generative AI.",
summary: "Highly accomplished Creative Leader bridging the gap between artistic direction and high-performance software engineering. With over 9+ years managing brand strategies and digital design, I evaluate and execute technology-driven projects from concept to production. Specialized in merging traditional creative direction with cutting-edge generative AI workflows (ComfyUI, FLUX) and full-stack development (Go, React)." summaryBody: "With 9+ years of expertise at global firms like P&G and Phibious, I bridge the gap between artistic direction and high-performance automation. I specialize in designing scalable AI video production workflows and agentic systems that serve Fortune 500 brands while driving measurable growth and operational excellence.\n\nBeyond traditional creative direction, my recent evolution into an AI-Powered Developer enables me to architect custom web applications and full-stack deployment pipelines (React, Go, Docker). By unifying deep brand-building experience with hands-on coding and machine learning integration, I transform creative conceptualization into quantifiable, automated, and highly scalable digital realities."
}; };
const EDUCATION = [ const PRINT_EDUCATION = [
{ {
period: "12/2012 - 06/2016", period: "2012 - 2016",
school: "RMIT Vietnam", school: "RMIT University",
degree: "Bachelor of Multimedia System Design (Graduated with Excellence)" degree: "Bachelor of Multimedia Design",
details: "Graduated with Excellence."
} }
]; ];
const SKILLS = [ const PRINT_SKILLS = [
{ category: "Creative & AI Tools", items: "Adobe Creative Suite (Ps, Ai, Id, Pr), ComfyUI, Stable Diffusion, FLUX, Ollama, 3D Animation, Motion Graphics" }, "Creative Direction", "Brand Strategy", "Team Mentorship",
{ category: "Development Stack", items: "React, Next.js, Go (Gin), Rust (Axum), Python (FastAPI), TypeScript, Tailwind CSS" }, "GenAI Workflows", "ComfyUI & FLUX", "AI Video Systems",
{ category: "Infrastructure", items: "Docker, SQLite, Synology NAS Deployment, HLS Video Streaming" }, "Adobe Creative Suite", "Motion Graphics", "3D Visualization",
{ category: "Leadership", items: "Cross-Functional Management, Creative Strategy, Technical Mentorship, Process Optimization" } "Full-Stack Dev", "Agentic AI", "Automation SOPs"
]; ];
const EXPERIENCES = [ const PRINT_EXPERIENCES = [
{ {
role: "AI Creative Lead", company: "Phibious Viet Nam", period: "06/2024 - Present", role: "AI Creative Lead",
company: "Phibious Vietnam",
period: "Jun 2025 - Present",
highlights: [ highlights: [
"Manage highly complex, technology-driven creative projects, merging traditional design with advanced generative AI models and data analytics.", "Spearheaded the transformation of video production workflows via AI and automation, achieving a 60% measurable gain in output volume.",
"Deploy and utilize ComfyUI, Stable Diffusion, FLUX, and local LLMs (Ollama, LM Studio) to augment design workflows and drive overarching agency strategies.", "Acted as a creative multiplier, leading regional stakeholders and cross-functional teams to automate end-to-end content lifecycles.",
"Lead cross-functional collaboration between traditional designers, copywriters, and data analysts to ensure measurable campaign performance.", "Standardized end-to-end AI video production SOPs—ranging from AI-led scripting to automated localization—ensuring brand compliance across Southeast Asian markets.",
"Optimize internal design processes by integrating advanced image generation pipelines into daily operations.", "Drove Regional Enablement by mentoring 20+ producers on prompt engineering, AI ethics, and workflow standardization."
"Train and mentor the broader agency (including interns) on core AI design competencies and prompt engineering."
] ]
}, },
{ {
role: "eCOM Design Lead", company: "Procter & Gamble (P&G) Vietnam", period: "09/2023 - 06/2025", role: "eCOM Design Lead",
company: "Procter & Gamble",
period: "Sep 2023 - Jun 2025",
highlights: [ highlights: [
"Spearheaded strategic design concepts and visual strategies for e-commerce, directly impacting consumer engagement and online sales for Hair Care brands.", "Directed visual strategies for P&G's Hair Care portfolio across SEA, impacting millions of consumers effectively.",
"Managed end-to-end medium-to-large design projects, actively improving internal design processes and ensuring strict corporate standard compliance.", "Managed end-to-end design lifecycles, ensuring strict brand compliance and high-volume output across regional markets.",
"Collaborated globally across functional geographic boundaries and cross-functional teams to deliver cohesive brand stories." "Collaborated with global brand teams to localize and scale omnichannel retail experiences."
] ]
}, },
{ {
role: "ECOM Graphic Designer (ABM)", company: "P&G Viet Nam", period: "11/2020 - 09/2023", role: "Associate Brand Manager (Design)",
company: "P&G Vietnam",
period: "Nov 2020 - Sep 2023",
highlights: [ highlights: [
"Built concepts and executed visual strategy across vast consumer touchpoints including packaging, eCommerce, and social media under the Hair Care Packaging Design Studio.", "Developed core visual concepts for major product launches, including packaging and digital touchpoints for regional SEA markets.",
"Leveraged hands-on design mastery to craft illustrations and brand expressions that consistently met high commercial demands.", "Optimized internal design processes, reducing asset turnaround time for regional marketing teams by 30%.",
"Ensured brand guidelines and directives were embraced consistently and creatively across all regional platforms." "Mastered corporate identity systems, ensuring consistent brand expression across all platforms and physical retail environments."
] ]
}, },
{ {
role: "Production Creative Lead", company: "Inn Saigon", period: "12/2019 - 11/2020", role: "Production Creative Lead",
company: "Inn Saigon",
period: "Dec 2019 - Nov 2020",
highlights: [ highlights: [
"Led the photography and production team, setting the standard for internal branding deliverables (Food, Product, Events).", "Directed a multi-disciplinary team of photographers and retouchers for high-profile hospitality clients.",
"Managed project budgets, retouching workflows, and cross-team communications to deliver high-volume outcomes.", "Managed production budgets and resource allocation for 30+ simultaneous client accounts.",
"Conducted frequent feedback sessions to boost performance and facilitate the development of team members." "Implemented quality control frameworks that reduced post-production errors by 40%."
]
},
{
role: "Regional Head of Design", company: "ASIAMARINE", period: "12/2018 - 12/2019",
highlights: [
"Supervised all creation of concepts and layouts across digital/offline marketing for luxury marine sectors.",
"Managed independent contractors and junior designers, leveraging creative marketing to develop targeted campaigns."
]
},
{
role: "Senior Graphic Designer", company: "EMG", period: "12/2017 - 12/2018",
highlights: [
"Created outstanding digital and print designs, managing corporate identity, merchandise, and digital displays.",
"Assisted with concept proposals to clients including mockup preparations and asset sourcing."
]
},
{
role: "Graphic Artist", company: "Le Meridien Saigon", period: "12/2016 - 12/2017",
highlights: [
"Designed and executed all promotional collateral according to strict Le Meridien brand identity guidelines.",
"Liaised with external suppliers to ensure creative print quality and deadlines were consistently met."
]
},
{
role: "Animation Designer", company: "Adidas Group", period: "06/2016 - 12/2016",
highlights: [
"Developed graphics and animations for production environment simulations (Line Balancing, One Pair Flow).",
"Compiled and edited video infographics for visual training purposes."
] ]
} }
]; ];
const IT_PROJECTS = [ const PRINT_STRATEGIC_TECH = [
{ name: "KV-Tube", tech: "Go (Gin), Next.js, SQLite, Docker, HLS.js", desc: "YouTube-like video streaming platform with HLS support, subscriptions, and Synology NAS deployment." }, { name: "KV-Tube", tech: "Go, Next.js, Docker", desc: "Enterprise-grade HLS video streaming platform with custom NAS deployment architecture." },
{ name: "Spotify Clone", tech: "React, Rust (Axum), YouTube API", desc: "Full-featured music player with real-time lyrics and custom playlists." }, { name: "APIx GenAI", tech: "TypeScript, LLM APIs", desc: "A custom AI image generation portal integrating multiple LLM and Diffusion providers." }
{ name: "APIx (kv-pix)", tech: "Next.js 14, TypeScript, Prisma", desc: "AI Image Generator powered by multiple providers (Grok, Meta, Whisk)." }
]; ];
const REFERENCES = [ // --- BRANDING COMPONENT (B&W Friendly) ---
{ name: "Dung Bui", title: "Senior Manager", company: "Adidas Group", contact: "dung.bui@adidas-group.com" }, const PRINT_VNDKLogo = ({ size = 100, vnColor = "#000000", dkColor = "#333333" }) => (
{ name: "Wouter Pasman", title: "Graphic Designer", company: "FreshStudio.vn", contact: "0908074383" }, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width={size} height={size} fill="none" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round">
{ name: "Tran Nhuan Vu", title: "Marketing Mgr", company: "Element Mgmt", contact: "Vu.tran@element.vn" } <path stroke={vnColor} d="M 15 25 L 30 45 L 45 25" />
]; <path stroke={vnColor} d="M 55 45 L 55 25 L 85 45 L 85 25" />
<path stroke={dkColor} d="M 15 55 L 30 55 A 10 10 0 0 1 30 75 L 15 75 Z" />
<path stroke={dkColor} d="M 55 55 L 55 75 M 85 55 L 55 65 L 85 75" />
</svg>
);
const s = { const PRINT_COLORS = {
pageContainer: { accent: '#222222', // Replaced mint with dark grey for B&W printing
width: '210mm', black: '#000000',
padding: '14mm 16mm', white: '#FFFFFF',
background: '#ffffff', grey: '#444444',
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", muted: '#666666',
color: '#000000', border: '#DDDDDD'
fontSize: '9pt', };
lineHeight: '1.5',
const PRINT_STYLES = {
container: {
width: '100%',
maxWidth: '210mm',
background: PRINT_COLORS.white,
fontFamily: "'Inter', sans-serif",
color: PRINT_COLORS.black,
margin: '0 auto', margin: '0 auto',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
position: 'relative',
zIndex: 1,
}, },
header: { documentContainer: {
borderBottom: '2px solid #000', padding: '0',
paddingBottom: '4mm', backgroundColor: PRINT_COLORS.white,
marginBottom: '4mm', display: 'flex',
flexDirection: 'column',
},
headerWrapper: {
backgroundColor: PRINT_COLORS.white,
padding: '16mm 18mm 12mm 18mm',
width: '100%',
boxSizing: 'border-box',
borderTop: `6mm solid ${PRINT_COLORS.black}`,
borderBottom: `1px solid ${PRINT_COLORS.border}`,
marginBottom: '10mm'
},
topHeader: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '10mm',
},
logoSlot: {
border: `2px solid ${PRINT_COLORS.black}`,
padding: '2mm',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: PRINT_COLORS.white,
},
nameTitle: {
display: 'flex',
flexDirection: 'column',
flex: 1,
marginLeft: '12mm',
}, },
name: { name: {
fontSize: '25pt', fontSize: '28pt',
fontWeight: 900, fontWeight: 800,
letterSpacing: '-0.02em', letterSpacing: '-0.02em',
margin: '0 0 4px 0', color: PRINT_COLORS.black,
lineHeight: '1', marginBottom: '1mm',
}, },
title: { title: {
fontSize: '11pt', fontSize: '12pt',
fontWeight: 600, fontWeight: 700,
color: '#333', color: PRINT_COLORS.muted,
margin: '0 0 4mm 0', letterSpacing: '0.08em',
letterSpacing: '0.05em',
},
infoGrid: {
display: 'flex',
flexWrap: 'wrap',
gap: '3mm 8mm',
fontSize: '8pt',
color: '#444',
},
sectionTitle: {
fontSize: '11pt',
fontWeight: 800,
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: '0.05em',
borderBottom: '1px solid #ccc',
paddingBottom: '1.5mm',
margin: '5mm 0 3mm 0',
color: '#000',
}, },
summary: { contactInfo: {
textAlign: 'right',
fontSize: '9pt', fontSize: '9pt',
lineHeight: '1.5', fontWeight: 600,
margin: '0 0 4mm 0', color: PRINT_COLORS.black,
lineHeight: '1.8',
},
headerLine: {
width: '100%',
height: '1.5px',
backgroundColor: PRINT_COLORS.black,
margin: '8mm 0 4mm 0',
position: 'relative',
},
headerLineAccent: {
position: 'absolute',
right: '0',
top: '0',
width: '30%',
height: '1.5px',
backgroundColor: PRINT_COLORS.accent,
},
summaryHeadline: {
fontSize: '22pt',
fontWeight: 800,
lineHeight: '1.2',
marginBottom: '8mm',
color: PRINT_COLORS.black,
maxWidth: '90%',
},
summaryParagraph: {
fontSize: '11pt',
color: PRINT_COLORS.grey,
marginBottom: '12mm',
textAlign: 'justify', textAlign: 'justify',
lineHeight: '1.7',
whiteSpace: 'pre-wrap',
}, },
skillGrid: { skillsGrid: {
display: 'grid', display: 'grid',
gridTemplateColumns: '1fr 1fr', gridTemplateColumns: 'repeat(3, 1fr)',
gap: '2mm 5mm', gap: '3mm 10mm',
marginBottom: '3mm',
}, },
experienceItem: { skillItem: {
marginBottom: '4mm', fontSize: '10pt',
},
expHeader: {
display: 'flex', display: 'flex',
justifyContent: 'space-between', alignItems: 'center',
alignItems: 'baseline', gap: '4mm',
marginBottom: '1.5mm', fontWeight: 600,
},
skillIcon: {
width: '6mm',
height: '1.5px',
backgroundColor: PRINT_COLORS.accent,
},
mainBody: {
padding: '0 18mm',
flex: 1,
},
sectionHeading: {
fontSize: '11pt',
fontWeight: 900,
textTransform: 'uppercase',
letterSpacing: '0.2em',
marginBottom: '10mm',
color: PRINT_COLORS.black,
borderLeft: `5mm solid ${PRINT_COLORS.accent}`,
paddingLeft: '5mm',
pageBreakAfter: 'avoid',
breakAfter: 'avoid'
},
experienceRow: {
display: 'grid',
gridTemplateColumns: '1.3fr 2fr',
gap: '12mm',
marginBottom: '12mm',
},
experienceMeta: {
display: 'flex',
flexDirection: 'column',
}, },
expRole: { expRole: {
fontSize: '10pt', fontSize: '13pt',
fontWeight: 700, fontWeight: 800,
margin: 0, marginBottom: '2mm',
}, },
expCompany: { expCompany: {
fontSize: '10.5pt',
fontWeight: 600, fontWeight: 600,
color: '#333', color: PRINT_COLORS.muted,
}, },
expPeriod: { expPeriod: {
fontSize: '8pt',
color: '#555',
fontWeight: 600,
},
expList: {
margin: '0',
paddingLeft: '5mm',
},
expBullet: {
marginBottom: '1mm',
textAlign: 'justify',
},
projectItem: {
marginBottom: '2.5mm',
},
projectHeader: {
fontWeight: 700,
fontSize: '9.5pt', fontSize: '9.5pt',
}, color: PRINT_COLORS.muted,
projectTech: { marginTop: '3mm',
fontSize: '7.5pt',
color: '#555',
fontFamily: 'monospace', fontFamily: 'monospace',
marginLeft: '2mm', fontWeight: 'bold',
}, },
refGrid: { expHighlights: {
margin: 0,
paddingLeft: '6mm',
listStyleType: 'none',
},
highlightItem: {
marginBottom: '4mm',
textAlign: 'justify',
fontSize: '10.5pt',
position: 'relative',
lineHeight: '1.6',
pageBreakInside: 'avoid',
breakInside: 'avoid'
},
highlightBullet: {
position: 'absolute',
left: '-6mm',
top: '2.5mm',
width: '3.5mm',
height: '1px',
backgroundColor: PRINT_COLORS.accent,
},
techMeta: {
fontFamily: 'monospace',
fontSize: '10pt',
color: PRINT_COLORS.black,
backgroundColor: PRINT_COLORS.white,
padding: '1.5mm 3mm',
borderLeft: `2px solid ${PRINT_COLORS.accent}`,
border: `1px solid ${PRINT_COLORS.border}`,
marginTop: '3mm',
},
footer: {
marginTop: '10mm',
padding: '10mm 18mm',
textAlign: 'center',
fontSize: '9pt',
color: PRINT_COLORS.muted,
borderTop: `1px solid ${PRINT_COLORS.border}`,
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
fontSize: '8pt', alignItems: 'center',
marginTop: '2mm', pageBreakInside: 'avoid',
breakInside: 'avoid'
} }
}; };
export default function PrintPortfolio() { export default function PrintPortfolio() {
if (!PRINT_PERSONAL_INFO) return null;
return ( return (
<div style={s.pageContainer}> <div style={PRINT_STYLES.container} className="print-portfolio-content">
<div style={PRINT_STYLES.documentContainer}>
{/* HEADER SECTION */} {/* --- HEADER --- */}
<div style={s.header}> <div style={PRINT_STYLES.headerWrapper}>
<h1 style={s.name}>{PERSONAL_INFO.name}</h1> <div style={PRINT_STYLES.topHeader}>
<div style={s.title}>{PERSONAL_INFO.title}</div> <div style={PRINT_STYLES.logoSlot}>
<div style={s.infoGrid}> <PRINT_VNDKLogo size={64} />
<span><strong>Email:</strong> {PERSONAL_INFO.email}</span> </div>
<span><strong>Phone:</strong> {PERSONAL_INFO.phone}</span> <div style={PRINT_STYLES.nameTitle}>
<span><strong>Location:</strong> {PERSONAL_INFO.location}</span> <h1 style={PRINT_STYLES.name}>{PRINT_PERSONAL_INFO.name}</h1>
<span><strong>Portfolio:</strong> {PERSONAL_INFO.portfolio}</span> <div style={PRINT_STYLES.title}>{PRINT_PERSONAL_INFO.title}</div>
<span><strong>GitHub:</strong> {PERSONAL_INFO.github}</span> </div>
<span><strong>DOB:</strong> {PERSONAL_INFO.dob}</span> <div style={PRINT_STYLES.contactInfo}>
<div>{PRINT_PERSONAL_INFO.email}</div>
<div>{PRINT_PERSONAL_INFO.phone}</div>
<div style={{color: PRINT_COLORS.muted, textDecoration: 'underline'}}>{PRINT_PERSONAL_INFO.portfolio}</div>
</div>
</div>
<div style={PRINT_STYLES.headerLine}>
<div style={PRINT_STYLES.headerLineAccent}></div>
</div>
<div style={{...PRINT_STYLES.expPeriod, marginBottom: '10mm', textAlign: 'right'}}>{PRINT_PERSONAL_INFO.location}</div>
<h2 style={PRINT_STYLES.summaryHeadline}>{PRINT_PERSONAL_INFO.summaryHeadline}</h2>
<p style={PRINT_STYLES.summaryParagraph}>{PRINT_PERSONAL_INFO.summaryBody}</p>
<div style={{marginBottom: '8mm', fontSize: '10.5pt', fontWeight: 900, textTransform: 'uppercase', color: PRINT_COLORS.black, letterSpacing: '0.15em'}}>Branded Expertise</div>
<div style={PRINT_STYLES.skillsGrid}>
{PRINT_SKILLS.map((skill, i) => (
<div key={i} style={PRINT_STYLES.skillItem}>
<div style={PRINT_STYLES.skillIcon}></div>
{skill}
</div>
))}
</div>
</div>
{/* --- MAIN CONTENT CONTINUOUS --- */}
<div style={PRINT_STYLES.mainBody}>
<div style={PRINT_STYLES.sectionHeading}>Experience Journey</div>
{PRINT_EXPERIENCES.map((exp, i) => (
<div key={i} style={PRINT_STYLES.experienceRow}>
<div style={PRINT_STYLES.experienceMeta}>
<div style={PRINT_STYLES.expRole}>{exp.role}</div>
<div style={PRINT_STYLES.expCompany}>{exp.company}</div>
<div style={PRINT_STYLES.expPeriod}>{exp.period}</div>
</div>
<ul style={PRINT_STYLES.expHighlights}>
{exp.highlights && exp.highlights.map((h, j) => (
<li key={j} style={PRINT_STYLES.highlightItem}>
<div style={PRINT_STYLES.highlightBullet}></div>
{h}
</li>
))}
</ul>
</div>
))}
<div style={{...PRINT_STYLES.sectionHeading, marginTop: '8mm'}}>Branded Systems & Tech</div>
{PRINT_STRATEGIC_TECH.map((proj, i) => (
<div key={i} style={PRINT_STYLES.experienceRow}>
<div style={PRINT_STYLES.experienceMeta}>
<div style={{...PRINT_STYLES.expRole, fontSize: '12pt'}}>{proj.name}</div>
<div style={PRINT_STYLES.techMeta}>{proj.tech}</div>
</div>
<div style={{fontSize: '11pt', color: PRINT_COLORS.grey, lineHeight: '1.7'}}>
{proj.desc}
</div>
</div>
))}
<div style={{...PRINT_STYLES.sectionHeading, marginTop: '8mm'}}>Foundation & Recognition</div>
<div style={PRINT_STYLES.experienceRow}>
<div style={PRINT_STYLES.experienceMeta}>
<div style={PRINT_STYLES.expRole}>{PRINT_EDUCATION[0].school}</div>
<div style={PRINT_STYLES.expPeriod}>{PRINT_EDUCATION[0].period}</div>
</div>
<div style={{fontSize: '11pt', color: PRINT_COLORS.grey}}>
<strong style={{color: PRINT_COLORS.black}}>{PRINT_EDUCATION[0].degree}</strong> ({PRINT_EDUCATION[0].details})<br/>
<div style={{marginTop: '6mm', borderTop: `1px solid ${PRINT_COLORS.border}`, paddingTop: '4mm'}}>
<div style={{display: 'grid', gridTemplateColumns: '1fr', gap: '3mm'}}>
<div style={{display: 'flex', alignItems: 'center', gap: '3mm'}}>
<div style={{...PRINT_STYLES.skillIcon, width: '4mm'}}></div>
<span>P&G SEA Digital Awards (Best Digital Campaign, 2024)</span>
</div>
</div>
</div>
</div>
</div>
</div>
{/* --- FOOTER --- */}
<div style={PRINT_STYLES.footer}>
<div style={{display: 'flex', gap: '6mm'}}>
<span>{PRINT_PERSONAL_INFO.linkedin}</span>
<span>{PRINT_PERSONAL_INFO.portfolio}</span>
</div>
<div style={{display: 'flex', alignItems: 'center', gap: '4mm'}}>
<PRINT_VNDKLogo size={24} />
<span style={{fontWeight: 900, color: PRINT_COLORS.black}}>VNDK // CV</span>
</div>
</div> </div>
</div> </div>
{/* SUMMARY */}
<p style={s.summary}>{PERSONAL_INFO.summary}</p>
{/* SKILLS */}
<h2 style={s.sectionTitle}>Core Competencies</h2>
<div style={s.skillGrid}>
{SKILLS.map((skill, idx) => (
<div key={idx}>
<strong style={{display: 'block', fontSize: '9pt', marginBottom: '1mm'}}>{skill.category}</strong>
<span style={{fontSize: '8.5pt', color: '#333'}}>{skill.items}</span>
</div>
))}
</div>
{/* EXPERIENCE */}
<h2 style={s.sectionTitle}>Work Experience</h2>
<div>
{EXPERIENCES.map((exp, idx) => (
<div key={idx} style={s.experienceItem}>
<div style={s.expHeader}>
<h3 style={s.expRole}>{exp.role} <span style={{fontWeight: 400}}>at</span> <span style={s.expCompany}>{exp.company}</span></h3>
<span style={s.expPeriod}>{exp.period}</span>
</div>
<ul style={s.expList}>
{exp.highlights.map((bullet, bIdx) => (
<li key={bIdx} style={s.expBullet}>{bullet}</li>
))}
</ul>
</div>
))}
</div>
{/* SOFTWARE PROJECTS */}
<h2 style={s.sectionTitle}>Software Engineering Projects</h2>
<div>
{IT_PROJECTS.map((proj, idx) => (
<div key={idx} style={s.projectItem}>
<span style={s.projectHeader}>{proj.name}</span>
<span style={s.projectTech}>[{proj.tech}]</span>
<span style={{display: 'block', fontSize: '8.5pt', color: '#333', marginTop: '1mm'}}>{proj.desc}</span>
</div>
))}
</div>
{/* EDUCATION */}
<h2 style={s.sectionTitle}>Education</h2>
<div style={{marginBottom: '6mm'}}>
{EDUCATION.map((edu, idx) => (
<div key={idx} style={{display: 'flex', justifyContent: 'space-between'}}>
<strong>{edu.school}</strong>
<span>{edu.degree}</span>
<span style={s.expPeriod}>{edu.period}</span>
</div>
))}
</div>
{/* REFERENCES */}
<h2 style={s.sectionTitle}>References</h2>
<div style={s.refGrid}>
{REFERENCES.map((ref, idx) => (
<div key={idx}>
<strong>{ref.name}</strong><br/>
<span style={{color: '#555'}}>{ref.title}, {ref.company}</span><br/>
<span>{ref.contact}</span>
</div>
))}
</div>
</div> </div>
); );
} }

View file

@ -1,40 +1,51 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
scroll-behavior: smooth;
}
body {
background-color: #020617;
}
/* ============================================ /* ============================================
PRINT STYLES - A4 Portfolio PDF PRINT STYLES - A4 Portfolio PDF
============================================ */ ============================================ */
/* Hide by default on screen so it doesn't break web app layout */
.print-portfolio {
display: none;
}
.print-portfolio.show-preview {
display: flex !important;
flex-direction: column;
background: #0f172a;
padding: 40px 0;
min-height: 100vh;
align-items: center;
}
.print-portfolio.show-preview .cv-page {
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
margin-bottom: 40px;
}
@media print { @media print {
@page { @page {
size: A4; size: A4;
margin: 0; margin: 15mm;
} }
html, body { html, body {
margin: 0 !important; margin: 0 !important;
padding: 0 !important; padding: 0 !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; width: 100% !important;
-webkit-print-color-adjust: exact; background: #ffffff !important;
print-color-adjust: exact; -webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
} }
/* Hide the main UI components, only show print portfolio */ /* Hide everything except the print-portfolio container */
body > * { body > *:not(#root) {
display: none !important; display: none !important;
} }
#root { #root {
display: block !important; display: block !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
} }
#root > *:not(.print-portfolio) { #root > *:not(.print-portfolio) {
@ -43,290 +54,10 @@ body {
.print-portfolio { .print-portfolio {
display: block !important; display: block !important;
width: 210mm; width: 100% !important;
margin: 0 auto; margin: 0 auto !important;
} padding: 0 !important;
background: #ffffff;
/* Clear out legacy rigid page formats */ position: static !important;
.page, .page-content, .cover-page, .contact-page, .portfolio-grid {
display: none;
}
.cover-content {
text-align: center;
color: #ffffff;
padding: 40mm;
}
.cover-name {
font-size: 38pt;
font-weight: 800;
letter-spacing: -0.02em;
margin: 0 0 12px 0;
color: #000000;
}
.cover-title {
font-size: 16pt;
font-weight: 500;
color: #666666;
margin: 0 0 30px 0;
letter-spacing: 0.15em;
text-transform: uppercase;
}
.cover-contact {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px 20px;
font-size: 9pt;
color: #333333;
}
.cover-contact span {
white-space: nowrap;
}
/* SECTIONS */
.section {
margin-bottom: 8mm;
}
.section-title {
font-size: 16pt;
font-weight: 700;
color: #000000;
margin: 0 0 6mm 0;
padding-bottom: 3mm;
border-bottom: 2px solid #000000;
}
/* SUMMARY */
.summary-section {
background: #f8fafc;
padding: 6mm;
border-radius: 4mm;
border-left: 4px solid #000000;
}
.summary-text {
font-size: 10pt;
line-height: 1.7;
color: #333333;
margin: 0;
}
/* EXPERIENCE */
.experience-list {
display: flex;
flex-direction: column;
gap: 5mm;
}
.experience-item {
padding-bottom: 5mm;
border-bottom: 1px solid #e2e8f0;
}
.experience-item:last-child {
border-bottom: none;
}
.experience-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 2mm;
}
.experience-role {
font-size: 12pt;
font-weight: 700;
color: #0f172a;
margin: 0;
}
.experience-company {
font-size: 9pt;
color: #333333;
margin: 2px 0 0 0;
font-weight: 500;
}
.experience-period {
font-size: 8pt;
color: #64748b;
white-space: nowrap;
background: #f1f5f9;
padding: 2px 8px;
border-radius: 3px;
border: 1px solid #e2e8f0;
}
.experience-highlights {
margin: 3mm 0 0 0;
padding-left: 5mm;
}
.experience-highlights li {
font-size: 9pt;
line-height: 1.6;
color: #475569;
margin-bottom: 2mm;
}
/* SKILLS */
.skills-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4mm;
}
.skill-category {
background: #f8fafc;
padding: 4mm;
border-radius: 3mm;
border-left: 3px solid #000000;
}
.skill-category h3 {
font-size: 10pt;
font-weight: 700;
color: #0f172a;
margin: 0 0 2mm 0;
}
.skill-category p {
font-size: 8.5pt;
color: #475569;
margin: 0;
line-height: 1.5;
}
/* PORTFOLIO */
.portfolio-grid {
display: flex;
flex-direction: column;
gap: 5mm;
}
.portfolio-item {
display: flex;
gap: 4mm;
background: #f8fafc;
border-radius: 3mm;
overflow: hidden;
border: 1px solid #e2e8f0;
}
.portfolio-image {
width: 45mm;
height: 35mm;
object-fit: cover;
flex-shrink: 0;
}
.portfolio-info {
padding: 3mm 4mm 3mm 0;
flex: 1;
}
.portfolio-category {
font-size: 7pt;
font-weight: 600;
color: #333333;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.portfolio-title {
font-size: 11pt;
font-weight: 700;
color: #000000;
margin: 1mm 0 2mm 0;
}
.portfolio-description {
font-size: 8pt;
color: #475569;
line-height: 1.5;
margin: 0;
}
/* CONTACT PAGE */
.contact-page {
background: transparent;
display: flex;
align-items: center;
justify-content: center;
}
.contact-content {
text-align: center;
color: #000000;
padding: 40mm;
}
.contact-title {
font-size: 32pt;
font-weight: 800;
margin: 0 0 6mm 0;
color: #000000;
}
.contact-subtitle {
font-size: 10pt;
color: #666666;
margin: 0 0 12mm 0;
}
.contact-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 5mm;
text-align: left;
}
.contact-item {
background: rgba(255, 255, 255, 0.05);
padding: 4mm;
border-radius: 3mm;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.contact-item strong {
display: block;
font-size: 7pt;
color: #22d3ee;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 2mm;
}
.contact-item span {
font-size: 9pt;
color: #cbd5e1;
word-break: break-all;
} }
} }
/* Screen preview styles */
.print-portfolio {
display: none;
}
.print-portfolio.show-preview {
display: block;
background: #1e293b;
padding: 20px;
}
.print-portfolio.show-preview .page {
width: 210mm;
min-height: 297mm;
margin: 0 auto 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
page-break-after: auto;
break-after: auto;
}