Add nature-informed counseling portfolio site
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
.vite
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Docs placeholders
|
||||||
|
public/docs/.keep
|
||||||
+340
@@ -0,0 +1,340 @@
|
|||||||
|
{
|
||||||
|
"version": "v1",
|
||||||
|
"timestamp": 1776539423236,
|
||||||
|
"ruleHash": "e3b0c44298fc1c14",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"id": "await-in-loop",
|
||||||
|
"name": "Await in Loop",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Await in loop — sequential execution is slow, use Promise.all()",
|
||||||
|
"query": " (for_in_statement\n body: (statement_block\n (expression_statement\n (await_expression) @AWAIT)))\n (for_statement\n body: (statement_block\n (expression_statement\n (await_expression) @AWAIT)))\n (while_statement\n body: (statement_block\n (expression_statement\n (await_expression) @AWAIT)))",
|
||||||
|
"metavars": [
|
||||||
|
"AWAIT"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "console-statement",
|
||||||
|
"name": "Console Statement",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "{{METHOD}} — remove debug statements before committing",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (identifier) @OBJ (#eq? @OBJ \"console\")\n property: (property_identifier) @METHOD (#not-eq? @METHOD \"dbg\"))\n arguments: (arguments) @ARGS)\n\n# Skip matches inside test blocks — no-console-in-tests handles that case",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"METHOD",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "not_in_test_block",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "constructor-super",
|
||||||
|
"name": "Missing super() call in derived class constructor",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Constructor of derived class must call super() before accessing 'this'",
|
||||||
|
"query": " (class_declaration\n (class_heritage\n (extends_clause) @EXTENDS)\n body: (class_body\n (method_definition\n name: (property_identifier) @CONSTRUCTOR\n (#eq? @CONSTRUCTOR \"constructor\")\n body: (statement_block) @BODY)))",
|
||||||
|
"metavars": [
|
||||||
|
"EXTENDS",
|
||||||
|
"CONSTRUCTOR",
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "no_super_call",
|
||||||
|
"post_filter_params": "{}",
|
||||||
|
"defect_class": "correctness",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "debugger-statement",
|
||||||
|
"name": "Debugger Statement",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Debugger statement — remove before committing",
|
||||||
|
"query": " (debugger_statement) @DEBUGGER",
|
||||||
|
"metavars": [
|
||||||
|
"DEBUGGER"
|
||||||
|
],
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deep-nesting",
|
||||||
|
"name": "Deep Nesting",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Deep nesting (3+ levels) — consider early returns or extract functions",
|
||||||
|
"query": " [\n ;; Pattern 1: if inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement) @IF_NESTED)))))\n\n ;; Pattern 2: for inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (for_statement) @FOR_NESTED)))))\n\n ;; Pattern 3: while inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (while_statement) @WHILE_NESTED)))))\n\n ;; Pattern 4: try inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (try_statement) @TRY_NESTED)))))\n\n ;; Pattern 5: if inside for inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (for_statement\n body: (statement_block\n (if_statement) @IF_IN_FOR)))))\n\n ;; Pattern 6: if inside while inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (while_statement\n body: (statement_block\n (if_statement) @IF_IN_WHILE)))))\n\n ;; Pattern 7: for inside for inside for\n (statement_block\n (for_statement\n body: (statement_block\n (for_statement\n body: (statement_block\n (for_statement) @FOR_NESTED)))))\n ]",
|
||||||
|
"metavars": [
|
||||||
|
"IF_NESTED",
|
||||||
|
"FOR_NESTED",
|
||||||
|
"WHILE_NESTED",
|
||||||
|
"TRY_NESTED",
|
||||||
|
"IF_IN_FOR",
|
||||||
|
"IF_IN_WHILE"
|
||||||
|
],
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deep-promise-chain",
|
||||||
|
"name": "Deep Promise Chain (4+ levels)",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Promise chain {{M1}} → {{M2}} → {{M3}} → {{M4}} — consider async/await",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (call_expression\n function: (member_expression\n object: (call_expression\n function: (member_expression\n object: (call_expression\n function: (member_expression\n property: (property_identifier) @M1)\n arguments: (arguments))\n property: (property_identifier) @M2)\n arguments: (arguments))\n property: (property_identifier) @M3)\n arguments: (arguments))\n property: (property_identifier) @M4)\n arguments: (arguments))\n (#match? @M1 \"^(then|catch|finally)$\")\n (#match? @M2 \"^(then|catch|finally)$\")\n (#match? @M3 \"^(then|catch|finally)$\")\n (#match? @M4 \"^(then|catch|finally)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"M1",
|
||||||
|
"M2",
|
||||||
|
"M3",
|
||||||
|
"M4"
|
||||||
|
],
|
||||||
|
"defect_class": "async-misuse",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "empty-catch",
|
||||||
|
"name": "Empty Catch Block",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Empty catch block — properly handle or log the error",
|
||||||
|
"query": " (catch_clause\n (identifier) @ERR\n (statement_block) @BODY)\n\n# Post-filter: Check if body is effectively empty (ignoring comments)",
|
||||||
|
"metavars": [
|
||||||
|
"ERR",
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "empty_body",
|
||||||
|
"defect_class": "silent-error",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no-eval",
|
||||||
|
"name": "Eval Usage",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "eval() detected — security risk, never use eval",
|
||||||
|
"query": " (call_expression\n function: (identifier) @FUNC\n (#eq? @FUNC \"eval\")\n arguments: (arguments) @ARGS)",
|
||||||
|
"metavars": [
|
||||||
|
"FUNC",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardcoded-secrets",
|
||||||
|
"name": "Hardcoded Secret",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Hardcoded secret in variable assignment — use environment variables",
|
||||||
|
"query": " [\n (lexical_declaration\n (variable_declarator\n name: (identifier) @VARNAME\n value: (string)))\n (expression_statement\n (assignment_expression\n left: (identifier) @VARNAME\n right: (string)))\n ]",
|
||||||
|
"metavars": [
|
||||||
|
"VARNAME"
|
||||||
|
],
|
||||||
|
"post_filter": "check_secret_pattern",
|
||||||
|
"defect_class": "secrets",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "long-parameter-list",
|
||||||
|
"name": "Long Parameter List",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Function has {{PARAM_COUNT}} parameters — use object pattern",
|
||||||
|
"query": " (function_declaration\n name: (identifier) @NAME\n parameters: (formal_parameters) @PARAMS\n body: (statement_block) @BODY)",
|
||||||
|
"metavars": [
|
||||||
|
"NAME",
|
||||||
|
"PARAMS",
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "count_params",
|
||||||
|
"post_filter_params": "",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed-async-styles",
|
||||||
|
"name": "Mixed Async/Await and Promise Chains",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Mixed async/await + promise chains — use consistent async style",
|
||||||
|
"query": " (function_declaration\n (async_modifier)\n body: (statement_block) @BODY)\n\n# Post-filter: Check if body contains both await and .then()",
|
||||||
|
"metavars": [
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "has_mixed_async",
|
||||||
|
"defect_class": "async-misuse",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-ternary",
|
||||||
|
"name": "Nested Ternary",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Nested ternary — use if/else or early returns for clarity",
|
||||||
|
"query": " (ternary_expression\n consequence: (ternary_expression) @NESTED)\n (ternary_expression\n alternative: (ternary_expression) @NESTED)",
|
||||||
|
"metavars": [
|
||||||
|
"NESTED"
|
||||||
|
],
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no-console-in-tests",
|
||||||
|
"name": "Console Statement in Test",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "console.{{METHOD}} in test block — use proper assertions or logging",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (identifier) @OBJ (#eq? @OBJ \"console\")\n property: (property_identifier) @METHOD)\n arguments: (arguments) @ARGS)",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"METHOD",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "in_test_block",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no-dupe-class-members",
|
||||||
|
"name": "Duplicate class member",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Duplicate class member '$NAME' - previous declaration will be overwritten",
|
||||||
|
"query": " (class_body\n (method_definition\n name: (property_identifier) @NAME1\n (#match? @NAME1 \"^[^#]\"))\n (method_definition\n name: (property_identifier) @NAME2\n (#eq? @NAME1 @NAME2)))",
|
||||||
|
"metavars": [
|
||||||
|
"NAME1",
|
||||||
|
"NAME2"
|
||||||
|
],
|
||||||
|
"defect_class": "correctness",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sql-injection",
|
||||||
|
"name": "SQL Injection Risk",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "SQL injection risk — use parameterized queries, never interpolate into SQL",
|
||||||
|
"query": " (call_expression\n function: [\n (identifier) @SQL_FUNC\n (member_expression property: (property_identifier) @SQL_FUNC)\n ]\n arguments: (arguments\n (template_string (template_substitution) @INTERPOLATION))\n (#match? @SQL_FUNC \"^(query|execute|exec|run)$\"))",
|
||||||
|
"metavars": [
|
||||||
|
"SQL_FUNC",
|
||||||
|
"INTERPOLATION"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-command-injection",
|
||||||
|
"name": "Command Injection Sink",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Potential command injection sink — avoid child_process command execution with untrusted input",
|
||||||
|
"query": " [\n (call_expression\n function: (member_expression\n object: (identifier) @MOD\n property: (property_identifier) @FN)\n arguments: (arguments) @ARGS)\n (call_expression\n function: (member_expression\n object: (member_expression\n object: (identifier) @MOD\n property: (property_identifier) @NS)\n property: (property_identifier) @FN)\n arguments: (arguments) @ARGS)\n ]\n (#eq? @MOD \"child_process\")\n (#match? @FN \"^(exec|execSync)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"MOD",
|
||||||
|
"NS",
|
||||||
|
"FN",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_command_injection_sink",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-detached-async-call",
|
||||||
|
"name": "Detached Async Call",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Detached async call — ensure this Promise is awaited or explicitly handled",
|
||||||
|
"query": " (expression_statement\n (call_expression\n function: [\n (identifier) @FN\n (member_expression\n property: (property_identifier) @FN)\n ]\n arguments: (arguments) @ARGS))\n (#match? @FN \"(Async$|fetch$|request$)\")",
|
||||||
|
"metavars": [
|
||||||
|
"FN",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_detached_async_call",
|
||||||
|
"defect_class": "async-misuse",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-insecure-random",
|
||||||
|
"name": "Insecure Randomness",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Insecure randomness source detected — use crypto.getRandomValues or secure RNG APIs",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (identifier) @OBJ\n property: (property_identifier) @FN)\n arguments: (arguments) @ARGS)\n (#eq? @OBJ \"Math\")\n (#eq? @FN \"random\")",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"FN",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_insecure_random_source",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-ssrf",
|
||||||
|
"name": "SSRF Risk",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Potential SSRF sink — validate and allowlist outbound URLs",
|
||||||
|
"query": " [\n (call_expression\n function: (identifier) @FN\n arguments: (arguments [(identifier) (member_expression) (call_expression)] @URL))\n (call_expression\n function: (member_expression\n object: (identifier) @OBJ\n property: (property_identifier) @FN)\n arguments: (arguments [(identifier) (member_expression) (call_expression)] @URL))\n ]\n (#match? @FN \"^(fetch|get|post|put|patch|delete|request)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"FN",
|
||||||
|
"URL"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_ssrf_sink",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-weak-hash",
|
||||||
|
"name": "Weak Hash Primitive",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Weak hash primitive selected (md5/sha1) — use sha256+ for security-sensitive contexts",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n property: (property_identifier) @FN)\n arguments: (arguments\n (string (string_fragment) @ALG)\n (_)*))\n (#eq? @FN \"createHash\")\n (#match? @ALG \"^(md5|sha1)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"FN",
|
||||||
|
"ALG"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_weak_hash_algorithm",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unsafe-regex",
|
||||||
|
"name": "Dynamic Regex Construction",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Dynamic regex from user input — can cause ReDoS (Regular Expression Denial of Service)",
|
||||||
|
"query": " (new_expression\n constructor: (identifier) @CTOR\n (#eq? @CTOR \"RegExp\")\n arguments: (arguments\n (template_string\n (template_substitution) @INTERPOLATION) @PATTERN))",
|
||||||
|
"metavars": [
|
||||||
|
"CTOR",
|
||||||
|
"INTERPOLATION",
|
||||||
|
"PATTERN"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "variable-shadowing",
|
||||||
|
"name": "Variable Shadowing",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Variable '{{NAME}}' shadows a parameter — use a distinct name",
|
||||||
|
"query": " (function_declaration\n parameters: (formal_parameters\n (required_parameter\n pattern: (identifier) @PARAM))\n body: (statement_block\n (lexical_declaration\n (variable_declarator\n name: (identifier) @NAME))))",
|
||||||
|
"metavars": [
|
||||||
|
"PARAM",
|
||||||
|
"NAME"
|
||||||
|
],
|
||||||
|
"post_filter": "name_matches_param",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"clones": [],
|
||||||
|
"duplicatedLines": 0,
|
||||||
|
"totalLines": 683,
|
||||||
|
"percentage": 0
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-18T19:23:14.429Z"
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"issues": [],
|
||||||
|
"unusedExports": [],
|
||||||
|
"unusedFiles": [],
|
||||||
|
"unusedDeps": [],
|
||||||
|
"unlistedDeps": [],
|
||||||
|
"summary": "Failed to parse output"
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-18T19:23:15.706Z"
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
null
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-18T19:20:46.514Z"
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "TODO",
|
||||||
|
"message": "parse Peter's Resume.docx and hydrate from content.",
|
||||||
|
"file": "src/sections/Experience.tsx",
|
||||||
|
"line": 8,
|
||||||
|
"column": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-18T19:20:24.854Z"
|
||||||
|
}
|
||||||
Vendored
+34
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"version": "v1",
|
||||||
|
"timestamp": 1776539454611,
|
||||||
|
"ruleHash": "e3b0c44298fc1c14",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"id": "dangerously-set-inner-html",
|
||||||
|
"name": "Dangerously Set Inner HTML",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "tsx",
|
||||||
|
"message": "dangerouslySetInnerHTML — XSS risk, sanitize user input",
|
||||||
|
"query": " (jsx_attribute\n (property_identifier) @ATTR\n (#match? @ATTR \"dangerouslySetInnerHTML\"))",
|
||||||
|
"metavars": [
|
||||||
|
"ATTR"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no-nested-links",
|
||||||
|
"name": "Nested anchor tags",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "tsx",
|
||||||
|
"message": "Nested <a> tags are invalid HTML and cause unexpected behavior",
|
||||||
|
"query": " (jsx_element\n open_tag: (jsx_opening_element\n (identifier) @OUTER\n (#eq? @OUTER \"a\"))\n (jsx_element\n open_tag: (jsx_opening_element\n (identifier) @INNER\n (#eq? @INNER \"a\"))))",
|
||||||
|
"metavars": [
|
||||||
|
"OUTER",
|
||||||
|
"INNER"
|
||||||
|
],
|
||||||
|
"defect_class": "correctness",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"signature": "src/styles.css::📐 Cascade errors in 2 other file(s) — fix before finishing turn:\n<diagnostics file=\"src/App.tsx\">\n line 4, col 24 code=2307: Cannot find module './sections/Philosophy' or its corresponding type declarations.\n</diagnostics>\n<diagnostics file=\"src/sections/SkillsGallery.tsx\">\n line 1, col 41 code=2307: Cannot find module 'lucide-react' or its corresponding type declarations.\n</diagnostics>"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-18T19:12:28.804Z"
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
null
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-18T19:12:31.358Z"
|
||||||
|
}
|
||||||
+340
@@ -0,0 +1,340 @@
|
|||||||
|
{
|
||||||
|
"version": "v1",
|
||||||
|
"timestamp": 1776539381512,
|
||||||
|
"ruleHash": "e3b0c44298fc1c14",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"id": "await-in-loop",
|
||||||
|
"name": "Await in Loop",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Await in loop — sequential execution is slow, use Promise.all()",
|
||||||
|
"query": " (for_in_statement\n body: (statement_block\n (expression_statement\n (await_expression) @AWAIT)))\n (for_statement\n body: (statement_block\n (expression_statement\n (await_expression) @AWAIT)))\n (while_statement\n body: (statement_block\n (expression_statement\n (await_expression) @AWAIT)))",
|
||||||
|
"metavars": [
|
||||||
|
"AWAIT"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "console-statement",
|
||||||
|
"name": "Console Statement",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "{{METHOD}} — remove debug statements before committing",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (identifier) @OBJ (#eq? @OBJ \"console\")\n property: (property_identifier) @METHOD (#not-eq? @METHOD \"dbg\"))\n arguments: (arguments) @ARGS)\n\n# Skip matches inside test blocks — no-console-in-tests handles that case",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"METHOD",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "not_in_test_block",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "constructor-super",
|
||||||
|
"name": "Missing super() call in derived class constructor",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Constructor of derived class must call super() before accessing 'this'",
|
||||||
|
"query": " (class_declaration\n (class_heritage\n (extends_clause) @EXTENDS)\n body: (class_body\n (method_definition\n name: (property_identifier) @CONSTRUCTOR\n (#eq? @CONSTRUCTOR \"constructor\")\n body: (statement_block) @BODY)))",
|
||||||
|
"metavars": [
|
||||||
|
"EXTENDS",
|
||||||
|
"CONSTRUCTOR",
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "no_super_call",
|
||||||
|
"post_filter_params": "{}",
|
||||||
|
"defect_class": "correctness",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "debugger-statement",
|
||||||
|
"name": "Debugger Statement",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Debugger statement — remove before committing",
|
||||||
|
"query": " (debugger_statement) @DEBUGGER",
|
||||||
|
"metavars": [
|
||||||
|
"DEBUGGER"
|
||||||
|
],
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deep-nesting",
|
||||||
|
"name": "Deep Nesting",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Deep nesting (3+ levels) — consider early returns or extract functions",
|
||||||
|
"query": " [\n ;; Pattern 1: if inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement) @IF_NESTED)))))\n\n ;; Pattern 2: for inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (for_statement) @FOR_NESTED)))))\n\n ;; Pattern 3: while inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (while_statement) @WHILE_NESTED)))))\n\n ;; Pattern 4: try inside if inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (if_statement\n consequence: (statement_block\n (try_statement) @TRY_NESTED)))))\n\n ;; Pattern 5: if inside for inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (for_statement\n body: (statement_block\n (if_statement) @IF_IN_FOR)))))\n\n ;; Pattern 6: if inside while inside if\n (statement_block\n (if_statement\n consequence: (statement_block\n (while_statement\n body: (statement_block\n (if_statement) @IF_IN_WHILE)))))\n\n ;; Pattern 7: for inside for inside for\n (statement_block\n (for_statement\n body: (statement_block\n (for_statement\n body: (statement_block\n (for_statement) @FOR_NESTED)))))\n ]",
|
||||||
|
"metavars": [
|
||||||
|
"IF_NESTED",
|
||||||
|
"FOR_NESTED",
|
||||||
|
"WHILE_NESTED",
|
||||||
|
"TRY_NESTED",
|
||||||
|
"IF_IN_FOR",
|
||||||
|
"IF_IN_WHILE"
|
||||||
|
],
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deep-promise-chain",
|
||||||
|
"name": "Deep Promise Chain (4+ levels)",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Promise chain {{M1}} → {{M2}} → {{M3}} → {{M4}} — consider async/await",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (call_expression\n function: (member_expression\n object: (call_expression\n function: (member_expression\n object: (call_expression\n function: (member_expression\n property: (property_identifier) @M1)\n arguments: (arguments))\n property: (property_identifier) @M2)\n arguments: (arguments))\n property: (property_identifier) @M3)\n arguments: (arguments))\n property: (property_identifier) @M4)\n arguments: (arguments))\n (#match? @M1 \"^(then|catch|finally)$\")\n (#match? @M2 \"^(then|catch|finally)$\")\n (#match? @M3 \"^(then|catch|finally)$\")\n (#match? @M4 \"^(then|catch|finally)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"M1",
|
||||||
|
"M2",
|
||||||
|
"M3",
|
||||||
|
"M4"
|
||||||
|
],
|
||||||
|
"defect_class": "async-misuse",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "empty-catch",
|
||||||
|
"name": "Empty Catch Block",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Empty catch block — properly handle or log the error",
|
||||||
|
"query": " (catch_clause\n (identifier) @ERR\n (statement_block) @BODY)\n\n# Post-filter: Check if body is effectively empty (ignoring comments)",
|
||||||
|
"metavars": [
|
||||||
|
"ERR",
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "empty_body",
|
||||||
|
"defect_class": "silent-error",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no-eval",
|
||||||
|
"name": "Eval Usage",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "eval() detected — security risk, never use eval",
|
||||||
|
"query": " (call_expression\n function: (identifier) @FUNC\n (#eq? @FUNC \"eval\")\n arguments: (arguments) @ARGS)",
|
||||||
|
"metavars": [
|
||||||
|
"FUNC",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardcoded-secrets",
|
||||||
|
"name": "Hardcoded Secret",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Hardcoded secret in variable assignment — use environment variables",
|
||||||
|
"query": " [\n (lexical_declaration\n (variable_declarator\n name: (identifier) @VARNAME\n value: (string)))\n (expression_statement\n (assignment_expression\n left: (identifier) @VARNAME\n right: (string)))\n ]",
|
||||||
|
"metavars": [
|
||||||
|
"VARNAME"
|
||||||
|
],
|
||||||
|
"post_filter": "check_secret_pattern",
|
||||||
|
"defect_class": "secrets",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "long-parameter-list",
|
||||||
|
"name": "Long Parameter List",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Function has {{PARAM_COUNT}} parameters — use object pattern",
|
||||||
|
"query": " (function_declaration\n name: (identifier) @NAME\n parameters: (formal_parameters) @PARAMS\n body: (statement_block) @BODY)",
|
||||||
|
"metavars": [
|
||||||
|
"NAME",
|
||||||
|
"PARAMS",
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "count_params",
|
||||||
|
"post_filter_params": "",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed-async-styles",
|
||||||
|
"name": "Mixed Async/Await and Promise Chains",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Mixed async/await + promise chains — use consistent async style",
|
||||||
|
"query": " (function_declaration\n (async_modifier)\n body: (statement_block) @BODY)\n\n# Post-filter: Check if body contains both await and .then()",
|
||||||
|
"metavars": [
|
||||||
|
"BODY"
|
||||||
|
],
|
||||||
|
"post_filter": "has_mixed_async",
|
||||||
|
"defect_class": "async-misuse",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-ternary",
|
||||||
|
"name": "Nested Ternary",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Nested ternary — use if/else or early returns for clarity",
|
||||||
|
"query": " (ternary_expression\n consequence: (ternary_expression) @NESTED)\n (ternary_expression\n alternative: (ternary_expression) @NESTED)",
|
||||||
|
"metavars": [
|
||||||
|
"NESTED"
|
||||||
|
],
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no-console-in-tests",
|
||||||
|
"name": "Console Statement in Test",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "console.{{METHOD}} in test block — use proper assertions or logging",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (identifier) @OBJ (#eq? @OBJ \"console\")\n property: (property_identifier) @METHOD)\n arguments: (arguments) @ARGS)",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"METHOD",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "in_test_block",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no-dupe-class-members",
|
||||||
|
"name": "Duplicate class member",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Duplicate class member '$NAME' - previous declaration will be overwritten",
|
||||||
|
"query": " (class_body\n (method_definition\n name: (property_identifier) @NAME1\n (#match? @NAME1 \"^[^#]\"))\n (method_definition\n name: (property_identifier) @NAME2\n (#eq? @NAME1 @NAME2)))",
|
||||||
|
"metavars": [
|
||||||
|
"NAME1",
|
||||||
|
"NAME2"
|
||||||
|
],
|
||||||
|
"defect_class": "correctness",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sql-injection",
|
||||||
|
"name": "SQL Injection Risk",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "SQL injection risk — use parameterized queries, never interpolate into SQL",
|
||||||
|
"query": " (call_expression\n function: [\n (identifier) @SQL_FUNC\n (member_expression property: (property_identifier) @SQL_FUNC)\n ]\n arguments: (arguments\n (template_string (template_substitution) @INTERPOLATION))\n (#match? @SQL_FUNC \"^(query|execute|exec|run)$\"))",
|
||||||
|
"metavars": [
|
||||||
|
"SQL_FUNC",
|
||||||
|
"INTERPOLATION"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-command-injection",
|
||||||
|
"name": "Command Injection Sink",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Potential command injection sink — avoid child_process command execution with untrusted input",
|
||||||
|
"query": " [\n (call_expression\n function: (member_expression\n object: (identifier) @MOD\n property: (property_identifier) @FN)\n arguments: (arguments) @ARGS)\n (call_expression\n function: (member_expression\n object: (member_expression\n object: (identifier) @MOD\n property: (property_identifier) @NS)\n property: (property_identifier) @FN)\n arguments: (arguments) @ARGS)\n ]\n (#eq? @MOD \"child_process\")\n (#match? @FN \"^(exec|execSync)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"MOD",
|
||||||
|
"NS",
|
||||||
|
"FN",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_command_injection_sink",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-detached-async-call",
|
||||||
|
"name": "Detached Async Call",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Detached async call — ensure this Promise is awaited or explicitly handled",
|
||||||
|
"query": " (expression_statement\n (call_expression\n function: [\n (identifier) @FN\n (member_expression\n property: (property_identifier) @FN)\n ]\n arguments: (arguments) @ARGS))\n (#match? @FN \"(Async$|fetch$|request$)\")",
|
||||||
|
"metavars": [
|
||||||
|
"FN",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_detached_async_call",
|
||||||
|
"defect_class": "async-misuse",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-insecure-random",
|
||||||
|
"name": "Insecure Randomness",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Insecure randomness source detected — use crypto.getRandomValues or secure RNG APIs",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n object: (identifier) @OBJ\n property: (property_identifier) @FN)\n arguments: (arguments) @ARGS)\n (#eq? @OBJ \"Math\")\n (#eq? @FN \"random\")",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"FN",
|
||||||
|
"ARGS"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_insecure_random_source",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-ssrf",
|
||||||
|
"name": "SSRF Risk",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Potential SSRF sink — validate and allowlist outbound URLs",
|
||||||
|
"query": " [\n (call_expression\n function: (identifier) @FN\n arguments: (arguments [(identifier) (member_expression) (call_expression)] @URL))\n (call_expression\n function: (member_expression\n object: (identifier) @OBJ\n property: (property_identifier) @FN)\n arguments: (arguments [(identifier) (member_expression) (call_expression)] @URL))\n ]\n (#match? @FN \"^(fetch|get|post|put|patch|delete|request)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"OBJ",
|
||||||
|
"FN",
|
||||||
|
"URL"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_ssrf_sink",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ts-weak-hash",
|
||||||
|
"name": "Weak Hash Primitive",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Weak hash primitive selected (md5/sha1) — use sha256+ for security-sensitive contexts",
|
||||||
|
"query": " (call_expression\n function: (member_expression\n property: (property_identifier) @FN)\n arguments: (arguments\n (string (string_fragment) @ALG)\n (_)*))\n (#eq? @FN \"createHash\")\n (#match? @ALG \"^(md5|sha1)$\")",
|
||||||
|
"metavars": [
|
||||||
|
"FN",
|
||||||
|
"ALG"
|
||||||
|
],
|
||||||
|
"post_filter": "ts_weak_hash_algorithm",
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unsafe-regex",
|
||||||
|
"name": "Dynamic Regex Construction",
|
||||||
|
"severity": "error",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Dynamic regex from user input — can cause ReDoS (Regular Expression Denial of Service)",
|
||||||
|
"query": " (new_expression\n constructor: (identifier) @CTOR\n (#eq? @CTOR \"RegExp\")\n arguments: (arguments\n (template_string\n (template_substitution) @INTERPOLATION) @PATTERN))",
|
||||||
|
"metavars": [
|
||||||
|
"CTOR",
|
||||||
|
"INTERPOLATION",
|
||||||
|
"PATTERN"
|
||||||
|
],
|
||||||
|
"defect_class": "injection",
|
||||||
|
"inline_tier": "blocking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "variable-shadowing",
|
||||||
|
"name": "Variable Shadowing",
|
||||||
|
"severity": "warning",
|
||||||
|
"language": "typescript",
|
||||||
|
"message": "Variable '{{NAME}}' shadows a parameter — use a distinct name",
|
||||||
|
"query": " (function_declaration\n parameters: (formal_parameters\n (required_parameter\n pattern: (identifier) @PARAM))\n body: (statement_block\n (lexical_declaration\n (variable_declarator\n name: (identifier) @NAME))))",
|
||||||
|
"metavars": [
|
||||||
|
"PARAM",
|
||||||
|
"NAME"
|
||||||
|
],
|
||||||
|
"post_filter": "name_matches_param",
|
||||||
|
"defect_class": "safety",
|
||||||
|
"inline_tier": "review"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+29644
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"vscode-json-language-server": {
|
||||||
|
"choice": "no",
|
||||||
|
"timestamp": 1776539450467
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"files": {
|
||||||
|
"src/main.tsx": {
|
||||||
|
"latest": {
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:12:58.698Z",
|
||||||
|
"mi": 66.3,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 0,
|
||||||
|
"lines": 9,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 3.04
|
||||||
|
},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:12:58.698Z",
|
||||||
|
"mi": 66.3,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 0,
|
||||||
|
"lines": 9,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 3.04
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trend": "stable"
|
||||||
|
},
|
||||||
|
"tailwind.config.js": {
|
||||||
|
"latest": {
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:13:07.786Z",
|
||||||
|
"mi": 83.1,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 0,
|
||||||
|
"lines": 7,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 2.75
|
||||||
|
},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:13:07.786Z",
|
||||||
|
"mi": 83.1,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 0,
|
||||||
|
"lines": 7,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 2.75
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trend": "stable"
|
||||||
|
},
|
||||||
|
"src/sections/Experience.tsx": {
|
||||||
|
"latest": {
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:22:03.434Z",
|
||||||
|
"mi": 45.7,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 3,
|
||||||
|
"lines": 47,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 4.97
|
||||||
|
},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:22:03.434Z",
|
||||||
|
"mi": 45.7,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 3,
|
||||||
|
"lines": 47,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 4.97
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trend": "stable"
|
||||||
|
},
|
||||||
|
"postcss.config.js": {
|
||||||
|
"latest": {
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:22:17.935Z",
|
||||||
|
"mi": 78.1,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 0,
|
||||||
|
"lines": 6,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 2.32
|
||||||
|
},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"commit": "unknown",
|
||||||
|
"timestamp": "2026-04-18T19:22:17.935Z",
|
||||||
|
"mi": 78.1,
|
||||||
|
"cognitive": 0,
|
||||||
|
"nesting": 0,
|
||||||
|
"lines": 6,
|
||||||
|
"maxCyclomatic": 1,
|
||||||
|
"entropy": 2.32
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trend": "stable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"capturedAt": "2026-04-18T19:22:22.936Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"files": {},
|
||||||
|
"turnCycles": 0,
|
||||||
|
"maxCycles": 3,
|
||||||
|
"lastUpdated": "2026-04-18T19:23:15.706Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Leadership Counseling Portfolio (Peter-William L. Myers)
|
||||||
|
|
||||||
|
Nature-informed, serene React + Tailwind portfolio.
|
||||||
|
|
||||||
|
## Next
|
||||||
|
- Scaffold React+Tailwind in this folder
|
||||||
|
- Build sections: Introduction, Philosophy, Skills (P1/P2 gallery), Experience (from Resume docx), Credentials (grid + downloads)
|
||||||
|
|
||||||
|
Docs source:
|
||||||
|
- `docs/Peter's Resume.docx`
|
||||||
|
- `docs/ActiveMDLCPC.pdf`
|
||||||
|
- `docs/PC014692 (1).pdf`
|
||||||
|
- `docs/NIT for SUD.pptx`
|
||||||
|
- `docs/Presentation1.pptx`
|
||||||
|
- `docs/Presentation2.pptx`
|
||||||
|
- `docs/Preview - Call for Proposals - 2026 ACA Annual Conference & Expo.pdf`
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#23463A" />
|
||||||
|
<title>Leadership Counseling Portfolio</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+2135
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "peter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Nature-informed, serene React + Tailwind portfolio.",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"doc": "docs"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.2.5",
|
||||||
|
"react-dom": "^19.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.2.2",
|
||||||
|
"@types/react": "^19.2.14",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"autoprefixer": "^10.5.0",
|
||||||
|
"docx": "^9.6.1",
|
||||||
|
"libreoffice-convert": "^1.8.1",
|
||||||
|
"lucide-react": "^1.8.0",
|
||||||
|
"mammoth": "^1.12.0",
|
||||||
|
"postcss": "^8.5.10",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
|
"tesseract.js": "^7.0.0",
|
||||||
|
"typescript": "^6.0.3",
|
||||||
|
"vite": "^8.0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
+30
@@ -0,0 +1,30 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import NavBar from './components/NavBar'
|
||||||
|
import Introduction from './sections/Introduction'
|
||||||
|
import Philosophy from './sections/Philosophy'
|
||||||
|
import SkillsGallery from './sections/SkillsGallery'
|
||||||
|
import Experience from './sections/Experience'
|
||||||
|
import Credentials from './sections/Credentials'
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
useEffect(() => {
|
||||||
|
// Smooth scroll for anchor links
|
||||||
|
document.documentElement.style.scrollBehavior = 'smooth'
|
||||||
|
return () => {
|
||||||
|
document.documentElement.style.scrollBehavior = ''
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-50 text-slate-800">
|
||||||
|
<NavBar />
|
||||||
|
<main className="pt-16">
|
||||||
|
<Introduction />
|
||||||
|
<Philosophy />
|
||||||
|
<SkillsGallery />
|
||||||
|
<Experience />
|
||||||
|
<Credentials />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
type NavItem = { href: string; label: string }
|
||||||
|
|
||||||
|
const navItems: NavItem[] = [
|
||||||
|
{ href: '#introduction', label: 'Introduction' },
|
||||||
|
{ href: '#philosophy', label: 'Philosophy' },
|
||||||
|
{ href: '#skills', label: 'Skills' },
|
||||||
|
{ href: '#experience', label: 'Experience' },
|
||||||
|
{ href: '#documents', label: 'Documents' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function NavBar() {
|
||||||
|
return (
|
||||||
|
<header className="fixed inset-x-0 top-0 z-50 border-b border-slate-200/70 bg-white/80 backdrop-blur">
|
||||||
|
<nav className="mx-auto flex max-w-6xl items-center justify-between px-4 py-3" aria-label="Primary">
|
||||||
|
<a href="#introduction" className="font-semibold tracking-wide text-slate-800">
|
||||||
|
Peter Myers
|
||||||
|
</a>
|
||||||
|
<ul className="hidden items-center gap-6 md:flex">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<li key={item.href}>
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
className="text-sm font-medium text-slate-700 hover:text-slate-900 focus:outline-none focus:ring-2 focus:ring-emerald-500/60 rounded"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<a
|
||||||
|
href="#introduction"
|
||||||
|
className="md:hidden text-sm font-medium text-slate-700 hover:text-slate-900"
|
||||||
|
aria-label="Jump to introduction"
|
||||||
|
>
|
||||||
|
Menu
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export default function SectionHeader({ title }: { title: string }) {
|
||||||
|
return (
|
||||||
|
<h2 className="font-serif text-2xl font-semibold text-slate-900 md:text-3xl">{title}</h2>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App'
|
||||||
|
import './styles.css'
|
||||||
|
import './theme/tokens.css'
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
type CredentialCard = {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
fileHref?: string
|
||||||
|
fileLabel?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const certifications: CredentialCard[] = [
|
||||||
|
{
|
||||||
|
title: 'Active MD LCPC',
|
||||||
|
description: 'License PC014692',
|
||||||
|
fileHref: '/docs/PC014692 (1).pdf',
|
||||||
|
fileLabel: 'Download PDF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Active MD LCPC (Certificate)',
|
||||||
|
description: 'Document for active credential status.',
|
||||||
|
fileHref: '/docs/ActiveMDLCPC.pdf',
|
||||||
|
fileLabel: 'Download PDF',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const presentations: CredentialCard[] = [
|
||||||
|
{
|
||||||
|
title: 'Presentations & Proposals',
|
||||||
|
description: 'NIT for SUD',
|
||||||
|
fileHref: '/docs/NIT for SUD.pptx',
|
||||||
|
fileLabel: 'Download / Open',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '2026 ACA Preview-Call for Proposals',
|
||||||
|
description: 'Conference proposal preview.',
|
||||||
|
fileHref:
|
||||||
|
'/docs/Preview - Call for Proposals - 2026 ACA Annual Conference & Expo.pdf',
|
||||||
|
fileLabel: 'Download / Open',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const phd: CredentialCard[] = [
|
||||||
|
{
|
||||||
|
title: 'PhD Candidacy',
|
||||||
|
description: 'Counselor Education and Supervision at Waynesburg University.',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function Credentials() {
|
||||||
|
const all = [
|
||||||
|
{ heading: 'Certifications', cards: certifications },
|
||||||
|
{ heading: 'Presentations & Proposals', cards: presentations },
|
||||||
|
{ heading: 'PhD Candidacy', cards: phd },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="documents" className="mx-auto max-w-6xl px-4 py-12 md:py-16">
|
||||||
|
<h2 className="font-serif text-2xl font-semibold text-slate-900 md:text-3xl">
|
||||||
|
Credentials & Publications
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-slate-700">
|
||||||
|
Downloadable documents linked from the site’s docs folder.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-10 space-y-10">
|
||||||
|
{all.map((group) => (
|
||||||
|
<div key={group.heading}>
|
||||||
|
<h3 className="text-base font-semibold text-slate-900">{group.heading}</h3>
|
||||||
|
<div className="mt-4 grid gap-6 md:grid-cols-2">
|
||||||
|
{group.cards.map((c) => (
|
||||||
|
<article key={c.title} className="rounded-3xl border border-slate-200 bg-white/70 p-6">
|
||||||
|
<h4 className="font-serif text-lg font-semibold text-slate-900">{c.title}</h4>
|
||||||
|
<p className="mt-2 text-sm leading-relaxed text-slate-700">{c.description}</p>
|
||||||
|
|
||||||
|
{c.fileHref ? (
|
||||||
|
<a
|
||||||
|
href={c.fileHref}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="mt-5 inline-flex items-center rounded-xl bg-emerald-700 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-800 focus:outline-none focus:ring-2 focus:ring-emerald-500/60"
|
||||||
|
aria-label={c.fileLabel ? `${c.fileLabel}: ${c.title}` : `Open ${c.title}`}
|
||||||
|
>
|
||||||
|
{c.fileLabel ?? 'Open'}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="mt-5 rounded-xl bg-slate-50 px-4 py-2 text-xs font-semibold text-slate-700">
|
||||||
|
Credential info
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
import type { ExperienceItem } from '../utils/parseResume'
|
||||||
|
import { parseResumeDocx } from '../utils/parseResume'
|
||||||
|
|
||||||
|
export default function Experience() {
|
||||||
|
const [items, setItems] = useState<ExperienceItem[] | null>(null)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
;(async () => {
|
||||||
|
try {
|
||||||
|
const parsed = await parseResumeDocx('/docs/Peter\'s Resume.docx')
|
||||||
|
if (!cancelled) setItems(parsed)
|
||||||
|
} catch (e) {
|
||||||
|
if (!cancelled) setError(String(e))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const sorted = useMemo(() => {
|
||||||
|
if (!items?.length) return []
|
||||||
|
// best-effort sort: if span has two years, use end year else first year
|
||||||
|
const pickEnd = (d: string) => {
|
||||||
|
const m = d.match(/(19\d{2}|20\d{2})\s*[-–]\s*(19\d{2}|20\d{2})/)
|
||||||
|
if (m) return Number(m[2])
|
||||||
|
const y = d.match(yearAny())
|
||||||
|
return y ? Number(y[0]) : 0
|
||||||
|
}
|
||||||
|
function yearAny() {
|
||||||
|
return /(19\d{2}|20\d{2})/
|
||||||
|
}
|
||||||
|
return [...items].sort((a, b) => pickEnd(b.date) - pickEnd(a.date))
|
||||||
|
}, [items])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="experience" className="mx-auto max-w-6xl px-4 py-12 md:py-16">
|
||||||
|
<h2 className="font-serif text-2xl font-semibold text-slate-900 md:text-3xl">
|
||||||
|
Counseling Experience
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-slate-700">
|
||||||
|
Resume-driven timeline (best-effort extraction from uploaded DOCX).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{error ? (
|
||||||
|
<div className="mt-6 rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-800">
|
||||||
|
Resume parse failed. Showing placeholder.
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="mt-8 space-y-5" aria-live="polite">
|
||||||
|
{sorted.length ? (
|
||||||
|
sorted.map((item, idx) => (
|
||||||
|
<article
|
||||||
|
key={`${item.date}-${idx}`}
|
||||||
|
className="rounded-3xl border border-slate-200 bg-white/70 p-6"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-1 md:flex-row md:items-baseline md:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-serif text-lg font-semibold text-slate-900">{item.role}</h3>
|
||||||
|
{item.org ? <p className="text-sm text-slate-700">{item.org}</p> : null}
|
||||||
|
</div>
|
||||||
|
<time className="text-sm font-semibold text-emerald-800">{item.date}</time>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{item.details?.length ? (
|
||||||
|
<ul className="mt-4 list-disc space-y-1 pl-5 text-sm text-slate-700">
|
||||||
|
{item.details.map((d, i) => (
|
||||||
|
<li key={i}>{d}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="rounded-3xl border border-slate-200 bg-white/70 p-6 text-sm text-slate-700">
|
||||||
|
Loading resume timeline…
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-5 text-xs text-slate-500">
|
||||||
|
Extraction is heuristic. Validate with source resume if high-stakes accuracy needed.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
export default function Introduction() {
|
||||||
|
return (
|
||||||
|
<section id="introduction" className="mx-auto max-w-6xl px-4 py-12 md:py-16">
|
||||||
|
<div className="grid items-center gap-10 md:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<div className="overflow-hidden rounded-2xl border border-slate-200 bg-emerald-50/40">
|
||||||
|
<img
|
||||||
|
src="/docs/headshot-placeholder.png"
|
||||||
|
alt="Peter-William L. Myers headshot"
|
||||||
|
className="h-80 w-full bg-emerald-100 object-cover md:h-96"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-serif text-3xl font-semibold text-slate-900">Introduction</h2>
|
||||||
|
<p className="mt-3 text-base leading-relaxed text-slate-700">
|
||||||
|
Peter-William L. Myers is a licensed professional counselor and certified nature-informed
|
||||||
|
therapist specializing in integrated treatment for co-occurring mental health and substance
|
||||||
|
use disorders. With 10 years of clinical experience, Peter blends trauma-informed care,
|
||||||
|
narrative therapy, and nature-based interventions to support holistic healing and
|
||||||
|
resilience. Passionate about making counseling accessible and culturally responsive, Peter
|
||||||
|
has developed presentations that harness the restorative power of the natural world. Peter
|
||||||
|
is committed to empowering clinicians with innovative tools to deepen client engagement and
|
||||||
|
recovery outcomes and is currently earning his PhD in Counselor Education and Supervision at
|
||||||
|
Waynesburg University.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-2xl border border-slate-200 bg-white/70 p-5">
|
||||||
|
<h3 className="font-serif text-xl font-semibold text-slate-900">Purpose</h3>
|
||||||
|
<p className="mt-2 text-base leading-relaxed text-slate-700">
|
||||||
|
The purpose of this Leadership Counseling Portfolio is to present the growth experience
|
||||||
|
connected to counselor identity development and provide evidence of competency,
|
||||||
|
understanding, and continued advanced in the counseling field that allows Peter to honor his
|
||||||
|
client’s journey of healing.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
export default function Philosophy() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="philosophy"
|
||||||
|
className="mx-auto max-w-6xl px-4 py-12 md:py-16"
|
||||||
|
>
|
||||||
|
<div className="rounded-3xl border border-amber-100/70 bg-amber-50/40 p-7 md:p-10">
|
||||||
|
<h2 className="font-serif text-2xl font-semibold text-slate-900 md:text-3xl">
|
||||||
|
Statement of Theoretical Orientation and Philosophy
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="mt-6 space-y-4 text-slate-700 leading-relaxed">
|
||||||
|
<p>
|
||||||
|
My theoretical orientation is grounded in an integrative framework that grows from Narrative
|
||||||
|
Therapy and Nature-Informed Therapy. This integrative framework is watered by trauma-responsive,
|
||||||
|
multicultural, and systems orientated perspectives.
|
||||||
|
Clients are not conceptualized as problems to be fixed but as people in relationship with the
|
||||||
|
stories we tell ourselves which have become internalized, the systems we journey, and the habitats
|
||||||
|
that shape our lived experience.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
While using Narrative Therapy, I view problems as separate from my client. Utilizing externalization,
|
||||||
|
reauthoring, and meaning-making, I provide a safe space for clients while they process their stories
|
||||||
|
influenced by trauma, culture, and oppression. Narrative Therapy allows the space to create an
|
||||||
|
alternative narrative rooted in resilience, autonomy, and identity reconstruction.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Nature-Informed Therapy allows me to use nature as my co-therapist. I incorporate nature metaphors,
|
||||||
|
grounding and earthing, and nature-based interventions to improve nervous system regulation,
|
||||||
|
emotional regulation, and connection. Nature gives us a nonjudgmental space where clients can freely
|
||||||
|
explore their identities, grief, emotions, and healing that are often less accessible thought traditional
|
||||||
|
talk therapy in an office setting which sometimes can be seen as “too clinical.”
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The integrative approach of Narrative and Nature-Informed therapies creates an understanding of healing
|
||||||
|
which is nonlinear. Utilizing both approaches allows me to flow between structured processes that hold
|
||||||
|
space for both evidence-based interventions and the natural unfolding of the client’s insight. My work is
|
||||||
|
deeply rooted in trauma-responsive care with an emphasis put on safety, autonomy, collaboration, and
|
||||||
|
empowerment.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Cultural humility is an important part of my clinical practice. Practicing cultural humility allows me to be
|
||||||
|
intentional in my engagement with clients of different races, genders, sexual orientations, oppression and
|
||||||
|
how these things influence the client’s narratives along with their access to care. My goal as a professional
|
||||||
|
counselor is to create a space that is affirming, inclusive, and responsive to the realities of my clients.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
As a master counselor, my goal is not to just facilitate change but to co-construct meaning, to support
|
||||||
|
identity creation, and foster sustainable healing that grows beyond the therapy room. My work reflects my
|
||||||
|
commitment to clinical excellence, ethical integrity, and the ongoing evolution of my professional counselor
|
||||||
|
identity.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { GraduationCap, Sparkles } from 'lucide-react'
|
||||||
|
|
||||||
|
type Presentation = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
icon: 'growth' | 'education'
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const presentations: Presentation[] = [
|
||||||
|
{
|
||||||
|
id: 'p1',
|
||||||
|
title: 'NIT for SUD',
|
||||||
|
description: 'Nature-informed interventions supporting substance use recovery.',
|
||||||
|
icon: 'education',
|
||||||
|
href: '/docs/NIT for SUD.pptx',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'p2',
|
||||||
|
title: 'ACA 2026 Preview-Call for Proposals',
|
||||||
|
description: 'Leadership-informed proposal draft for conference submission.',
|
||||||
|
icon: 'growth',
|
||||||
|
href:
|
||||||
|
'/docs/Preview - Call for Proposals - 2026 ACA Annual Conference & Expo.pdf',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function Icon({ kind }: { kind: Presentation['icon'] }) {
|
||||||
|
if (kind === 'growth') {
|
||||||
|
return <Sparkles className="h-6 w-6 text-emerald-700" aria-hidden="true" />
|
||||||
|
}
|
||||||
|
return <GraduationCap className="h-6 w-6 text-emerald-700" aria-hidden="true" />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SkillsGallery() {
|
||||||
|
return (
|
||||||
|
<section id="skills" className="mx-auto max-w-6xl px-4 py-12 md:py-16">
|
||||||
|
<div className="flex items-end justify-between gap-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-serif text-2xl font-semibold text-slate-900 md:text-3xl">
|
||||||
|
Demonstration of Counseling Skills
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-slate-700">
|
||||||
|
Select presentations and proposals showcasing leadership, clinical growth, and nature-informed
|
||||||
|
practice.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 grid gap-6 md:grid-cols-2">
|
||||||
|
{presentations.map((p) => (
|
||||||
|
<article
|
||||||
|
key={p.id}
|
||||||
|
className="group rounded-3xl border border-slate-200 bg-white/70 p-6 shadow-sm"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Icon kind={p.icon} />
|
||||||
|
<h3 className="font-serif text-lg font-semibold text-slate-900">{p.title}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 text-sm leading-relaxed text-slate-700">{p.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={p.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="mt-5 inline-flex items-center rounded-xl bg-emerald-700 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-800 focus:outline-none focus:ring-2 focus:ring-emerald-500/60"
|
||||||
|
aria-label={`View ${p.title}`}
|
||||||
|
>
|
||||||
|
View Presentation
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
export const natureTheme = {
|
||||||
|
colors: {
|
||||||
|
emeraldSage: {
|
||||||
|
50: '#f0fbf5',
|
||||||
|
100: '#dcf6e8',
|
||||||
|
200: '#b5e9cc',
|
||||||
|
300: '#7dd2a6',
|
||||||
|
400: '#45b783',
|
||||||
|
500: '#1f9a64',
|
||||||
|
600: '#168051',
|
||||||
|
700: '#0f6141',
|
||||||
|
800: '#0b4e35',
|
||||||
|
900: '#073a27',
|
||||||
|
},
|
||||||
|
parchment: {
|
||||||
|
50: '#fbf7ea',
|
||||||
|
100: '#f6efd7',
|
||||||
|
200: '#eadfbb',
|
||||||
|
300: '#dec894',
|
||||||
|
400: '#cfb56f',
|
||||||
|
500: '#b69f51',
|
||||||
|
600: '#977f43',
|
||||||
|
700: '#7a6535',
|
||||||
|
800: '#5d4d2a',
|
||||||
|
900: '#3d321d',
|
||||||
|
},
|
||||||
|
slateSoft: {
|
||||||
|
50: '#f6f7fb',
|
||||||
|
100: '#e8ebf5',
|
||||||
|
200: '#d1d7ec',
|
||||||
|
300: '#b7c2dc',
|
||||||
|
400: '#93a6c7',
|
||||||
|
500: '#6f86b5',
|
||||||
|
600: '#4f658f',
|
||||||
|
700: '#3c4d6a',
|
||||||
|
800: '#2d394f',
|
||||||
|
900: '#1f2a3a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { natureTheme } from './nature'
|
||||||
|
|
||||||
|
export const theme = natureTheme
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
:root{--color-emerald-500:#1f9a64;--color-slate-800:#1f2a3a;}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import mammoth from 'mammoth'
|
||||||
|
|
||||||
|
export type ExperienceItem = {
|
||||||
|
date: string
|
||||||
|
role: string
|
||||||
|
org: string
|
||||||
|
details: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const yearRe = /(19\d{2}|20\d{2})/g
|
||||||
|
|
||||||
|
function cleanLines(text: string) {
|
||||||
|
return text
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((l) => l.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickYearSpan(line: string) {
|
||||||
|
const years = line.match(yearRe)
|
||||||
|
if (!years?.length) return null
|
||||||
|
// prefer "YYYY – YYYY" patterns
|
||||||
|
const span = line.match(/(19\d{2}|20\d{2})\s*[-–]\s*(19\d{2}|20\d{2})/)
|
||||||
|
if (span) return span[0].replace(/\s+/g, ' ').trim()
|
||||||
|
if (years.length >= 1) return years[0]
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksLikeRoleOrg(line: string) {
|
||||||
|
const lc = line.toLowerCase()
|
||||||
|
return (
|
||||||
|
/(counsel|therap|clin|supervis|director|coordinator|intern|case|assistant)/i.test(line) &&
|
||||||
|
lc.length >= 6
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort: extract year-span + nearby role/org-ish lines.
|
||||||
|
export async function parseResumeDocx(url: string): Promise<ExperienceItem[]> {
|
||||||
|
const res = await fetch(url)
|
||||||
|
if (!res.ok) throw new Error(`Failed to fetch resume: ${res.status}`)
|
||||||
|
const buf = await res.arrayBuffer()
|
||||||
|
|
||||||
|
const result = await mammoth.extractRawText({
|
||||||
|
// mammoth expects ArrayBuffer/Buffer
|
||||||
|
arrayBuffer: buf as any,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const lines = cleanLines(String(result.value ?? ''))
|
||||||
|
|
||||||
|
// Find candidate lines with years.
|
||||||
|
const candidates = lines
|
||||||
|
.map((line, idx) => ({ line, idx, yearSpan: pickYearSpan(line) }))
|
||||||
|
.filter((x) => x.yearSpan)
|
||||||
|
|
||||||
|
if (!candidates.length) {
|
||||||
|
// fallback: single bucket
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
date: 'Experience',
|
||||||
|
role: 'Clinical Experience',
|
||||||
|
org: '',
|
||||||
|
details: lines.slice(0, 6),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: ExperienceItem[] = []
|
||||||
|
|
||||||
|
for (const c of candidates.slice(0, 12)) {
|
||||||
|
const context = lines.slice(Math.max(0, c.idx - 2), Math.min(lines.length, c.idx + 3))
|
||||||
|
const roleOrg = context.find((l) => looksLikeRoleOrg(l) && l !== c.line) ?? c.line
|
||||||
|
|
||||||
|
// naive split
|
||||||
|
const parts = roleOrg.split(/[-—–|•]/).map((p) => p.trim()).filter(Boolean)
|
||||||
|
|
||||||
|
const role = parts[0] || roleOrg
|
||||||
|
const org = parts[1] || ''
|
||||||
|
|
||||||
|
const details = context
|
||||||
|
.filter((l) => l !== roleOrg && l !== c.line)
|
||||||
|
.slice(0, 2)
|
||||||
|
|
||||||
|
items.push({ date: c.yearSpan!, role, org, details })
|
||||||
|
}
|
||||||
|
|
||||||
|
// de-dupe by date+role
|
||||||
|
const uniq: ExperienceItem[] = []
|
||||||
|
const seen = new Set<string>()
|
||||||
|
for (const it of items) {
|
||||||
|
const k = `${it.date}|${it.role}`
|
||||||
|
if (seen.has(k)) continue
|
||||||
|
seen.add(k)
|
||||||
|
uniq.push(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniq
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
import { natureTheme } from './src/theme/nature'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
content: ['./index.html', './src/**/*.{ts,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
emeraldSage: natureTheme.colors.emeraldSage,
|
||||||
|
parchment: natureTheme.colors.parchment,
|
||||||
|
slateSoft: natureTheme.colors.slateSoft,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user