Import current resume static site

This commit is contained in:
2026-07-11 08:17:34 -05:00
commit e13a69f85a
14 changed files with 1316 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.old/
tmp/
Microsoft.PowerShell.Core/
+4
View File
@@ -0,0 +1,4 @@
RewriteEngine On
RedirectMatch 301 ^/(blog|blog-full-post|contact|cover-latter|portfolio-full|project|test|index1|index2|old_index)\.html$ /
RedirectMatch 301 ^/process\.php$ /
+31
View File
@@ -0,0 +1,31 @@
# jasoneugenegarrison.com
Static resume site for Jason Eugene Garrison.
## Workflow
Use Git for normal rollback and history.
If a risky manual edit needs a temporary snapshot, place it in an external archive safe outside the repo after the matching Git commit is pushed.
## Legacy helper scripts
The site currently includes helper scripts from the pre-Git workflow. Prefer Git commits for routine changes. If you need the legacy snapshot helper, move resulting `.old` output to the external archive safe instead of keeping it in the repo.
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\save-old.ps1 -Label "before-homepage-edit"
```
After editing public files, refresh the permissions nginx needs:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\publish-permissions.ps1
```
To rebuild the downloadable resume files from the current site:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build-resume-assets.ps1
```
Snapshots are ignored by Git and should not be retained in the repo.
Binary file not shown.
Binary file not shown.
+172
View File
@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jason E. Garrison | IT Infrastructure Specialist</title>
<meta name="description" content="Resume of Jason Eugene Garrison - IT Infrastructure Specialist and field engineer with 20+ years of commercial support experience.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='4' fill='%23b91c1c'/%3E%3Ctext x='50%25' y='50%25' dy='.35em' text-anchor='middle' font-family='sans-serif' font-weight='bold' font-size='32' fill='white'%3EJG%3C/text%3E%3C/svg%3E" type="image/svg+xml">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Oswald:wght@500;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="site.css?v=20260615-1148" rel="stylesheet">
</head>
<body>
<main class="resume-paper">
<div class="action-bar" aria-label="Resume actions">
<a href="Resume_Jason_Eugene_Garrison.pdf?v=20260615-1214" class="btn-dl" download="Resume_Jason_Eugene_Garrison.pdf">
<i class="fa-solid fa-file-pdf" aria-hidden="true"></i>
<span>PDF</span>
</a>
<a href="Resume_Jason_Eugene_Garrison.docx?v=20260615-1214" class="btn-dl" download="Resume_Jason_Eugene_Garrison.docx">
<i class="fa-solid fa-file-word" aria-hidden="true"></i>
<span>DOCX</span>
</a>
<button id="print-resume" class="btn-dl" type="button">
<i class="fa-solid fa-print" aria-hidden="true"></i>
<span>Print</span>
</button>
<button id="theme-toggle" class="btn-dl icon-btn" type="button" title="Toggle theme" aria-label="Toggle theme">
<i class="fa-solid fa-moon" aria-hidden="true"></i>
</button>
</div>
<header class="resume-header">
<img src="jason.JPG" alt="Jason Garrison" class="profile-img">
<div class="header-content">
<h1>Jason Eugene Garrison</h1>
<p class="role">IT Infrastructure Specialist & Field Engineer</p>
<p class="availability">Available for infrastructure support, field engineering, and commercial IT operations.</p>
<address class="contact-grid">
<span class="contact-item"><i class="fa-solid fa-map-pin" aria-hidden="true"></i> Oak Grove, KY</span>
<span class="contact-item">
<i class="fa-solid fa-phone" aria-hidden="true"></i>
<button class="secure-link" type="button" data-contact="ky">Show KY Number</button>
</span>
<span class="contact-item">
<i class="fa-solid fa-phone" aria-hidden="true"></i>
<button class="secure-link" type="button" data-contact="tn">Show TN Number</button>
</span>
<span class="contact-item">
<i class="fa-solid fa-envelope" aria-hidden="true"></i>
<button class="secure-link" type="button" data-contact="email">Show Email</button>
</span>
<span class="contact-item">
<i class="fa-solid fa-globe" aria-hidden="true"></i>
<a href="https://www.jasoneugenegarrison.com">www.jasoneugenegarrison.com</a>
</span>
</address>
</div>
</header>
<div class="content-body">
<section class="section">
<h2 class="section-title">Professional Profile</h2>
<p class="profile-text">
Field-tested IT Infrastructure Specialist with more than 20 years of hands-on experience in network deployment, hardware diagnostics, and commercial support. Known for solving field problems under pressure in healthcare, correctional, commercial, and customer-facing environments.
</p>
<div class="strength-cloud" aria-label="Core strengths">
<span class="weight-3">Field Engineering</span>
<span class="weight-2">Network Deployment</span>
<span class="weight-2">Hardware Diagnostics</span>
<span>Commercial Support</span>
<span>Project Execution</span>
<span>Client Communication</span>
</div>
</section>
<section class="section compact-section">
<h2 class="section-title">Core Tools & Work Types</h2>
<p class="profile-text">
Network drops, hardware swaps, workstation repair, site surveys, MSP tickets, printer support, router and switch installs, customer handoff, and field documentation.
</p>
</section>
<section class="section">
<h2 class="section-title">Professional Experience</h2>
<article class="job-item">
<div class="job-head">
<h3 class="job-title">Independent Commercial IT Specialist</h3>
<p class="job-meta">2019 - Present</p>
</div>
<p class="job-sub">Independent Contractor | Tennessee & Kentucky</p>
<ul class="job-desc">
<li>Serve as a field engineer for national MSPs and direct commercial clients, handling technical tickets in healthcare facilities, correctional institutions, and other sensitive work sites.</li>
<li>Manage infrastructure projects from site assessment through installation, testing, and handoff.</li>
<li>Review scopes, cabling paths, and equipment needs to reduce waste while keeping systems reliable and serviceable.</li>
</ul>
</article>
<article class="job-item">
<div class="job-head">
<h3 class="job-title">Owner & Lead Technician</h3>
<p class="job-meta">2014 - 2019</p>
</div>
<p class="job-sub">Garrison Tech Services | Nashville, TN</p>
<ul class="job-desc">
<li>Founded and operated an independent computer repair and support business serving residential and small-business customers.</li>
<li>Built a profitable service operation through fast turnaround, clear communication, and repeat referrals.</li>
<li>Diagnosed and repaired thousands of machines while managing pricing, inventory, customer service, and local marketing.</li>
</ul>
</article>
<article class="job-item">
<div class="job-head">
<h3 class="job-title">Telecommunications Contractor</h3>
<p class="job-meta">2011 - 2014</p>
</div>
<p class="job-sub">ISP Infrastructure Contractors | Indiana</p>
<ul class="job-desc">
<li>Completed end-to-end ISP installations, including line work, customer-premise setup, and in-home network configuration.</li>
<li>Balanced technical installation work with customer-facing sales and service package upgrades.</li>
</ul>
</article>
<article class="job-item">
<div class="job-head">
<h3 class="job-title">Graphic Design & IT Consultant</h3>
<p class="job-meta">2004 - 2009</p>
</div>
<p class="job-sub">Grace Evangelistic Ministries | Nashville, TN</p>
<ul class="job-desc">
<li>Designed and prepared a bi-monthly donor newsletter, coordinating layout, production, and distribution.</li>
<li>Supported office computers, printers, and internal systems so administrative staff could work without avoidable downtime.</li>
</ul>
</article>
</section>
<section class="section">
<h2 class="section-title">Education & Background</h2>
<div class="edu-box">
<p class="edu-title">Self-Directed Professional Development</p>
<p class="edu-sub">Hands-on technical training through field work, repair, deployment, and independent study.</p>
<p class="edu-text">
My technical education has been built through direct experience: diagnosing failures, learning unfamiliar systems under pressure, and turning difficult field problems into working solutions.
</p>
<p class="edu-text">
That background has made me resourceful, self-directed, and comfortable learning new tools without needing a formal classroom before I can contribute.
</p>
</div>
</section>
<section class="section final-section">
<h2 class="section-title">Career Target</h2>
<p class="profile-text">
After more than two decades of independent technical work, I am ready for a dedicated long-term role where I can bring steady judgment, practical troubleshooting, and strong field ownership to a team. I am looking for an organization that values independent troubleshooting, clear communication, and practical ownership.
</p>
</section>
</div>
<footer>
<span>&copy; 2026 Jason Eugene Garrison</span>
<span>Last updated June 15, 2026</span>
</footer>
</main>
<script src="site.js?v=20260615-1148"></script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

+73
View File
@@ -0,0 +1,73 @@
$ErrorActionPreference = "Stop"
$siteRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).ProviderPath
$python = "C:\Users\jason\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe"
$chrome = "C:\Program Files\Google\Chrome\Application\chrome.exe"
$port = 8793
$pdfPath = Join-Path $siteRoot "Resume_Jason_Eugene_Garrison.pdf"
$docxPath = Join-Path $siteRoot "Resume_Jason_Eugene_Garrison.docx"
if (-not (Test-Path $python)) {
throw "Bundled Python was not found at $python"
}
if (-not (Test-Path $chrome)) {
throw "Chrome was not found at $chrome"
}
& $python (Join-Path $PSScriptRoot "generate-resume-docx.py") $siteRoot $docxPath
if ($LASTEXITCODE -ne 0) {
throw "DOCX generation failed with exit code $LASTEXITCODE"
}
$server = $null
try {
$server = Start-Process -FilePath $python `
-ArgumentList @("-m", "http.server", "$port", "--bind", "127.0.0.1", "--directory", $siteRoot) `
-WorkingDirectory $env:TEMP `
-WindowStyle Hidden `
-PassThru
$url = "http://127.0.0.1:$port/index.html?print=$(Get-Date -Format yyyyMMddHHmmss)"
$ready = $false
for ($i = 0; $i -lt 30; $i++) {
try {
$response = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 2
if ($response.StatusCode -eq 200 -and $response.Content -match "Jason Eugene Garrison") {
$ready = $true
break
}
} catch {
Start-Sleep -Milliseconds 500
}
}
if (-not $ready) {
throw "Local resume preview server was not reachable at $url"
}
& $chrome `
--headless=new `
--disable-gpu `
--disable-background-networking `
--disable-sync `
--metrics-recording-only `
--no-pdf-header-footer `
--print-to-pdf="$pdfPath" `
$url | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "PDF generation failed with exit code $LASTEXITCODE"
}
}
finally {
if ($server -and -not $server.HasExited) {
Stop-Process -Id $server.Id -Force
}
}
& (Join-Path $PSScriptRoot "publish-permissions.ps1")
Write-Host "Updated $pdfPath"
Write-Host "Updated $docxPath"
+214
View File
@@ -0,0 +1,214 @@
from pathlib import Path
import html
import re
import sys
from docx import Document
from docx.enum.section import WD_SECTION
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor
from lxml import html as lxml_html
CONTACTS = {
"Location": "Oak Grove, KY",
"Phone (KY)": "(270) 889-8072",
"Phone (TN)": "(865) 985-1641",
"Email": "jasoneugenegarrison@icloud.com",
"Website": "www.jasoneugenegarrison.com",
}
def clean(text):
return re.sub(r"\s+", " ", html.unescape(text or "")).strip()
def by_class(node, class_name, tag="*"):
xpath = f".//{tag}[contains(concat(' ', normalize-space(@class), ' '), ' {class_name} ')]"
return node.xpath(xpath)
def add_bottom_border(paragraph, color="b91c1c", size="12"):
p_pr = paragraph._p.get_or_add_pPr()
p_bdr = OxmlElement("w:pBdr")
bottom = OxmlElement("w:bottom")
bottom.set(qn("w:val"), "single")
bottom.set(qn("w:sz"), size)
bottom.set(qn("w:space"), "4")
bottom.set(qn("w:color"), color)
p_bdr.append(bottom)
p_pr.append(p_bdr)
def set_run(run, size=None, bold=None, color=None, all_caps=False):
if size:
run.font.size = Pt(size)
if bold is not None:
run.bold = bold
if color:
run.font.color.rgb = RGBColor.from_string(color)
if all_caps:
run.text = run.text.upper()
def add_heading(doc, text):
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(12)
p.paragraph_format.space_after = Pt(6)
run = p.add_run(text.upper())
run.bold = True
run.font.name = "Arial"
run.font.size = Pt(11)
run.font.color.rgb = RGBColor(185, 28, 28)
add_bottom_border(p, "e5e7eb", "6")
return p
def add_bullet(doc, text):
p = doc.add_paragraph(style="List Bullet")
p.paragraph_format.left_indent = Inches(0.22)
p.paragraph_format.first_line_indent = Inches(-0.12)
p.paragraph_format.space_after = Pt(3)
run = p.add_run(text)
run.font.name = "Arial"
run.font.size = Pt(9.5)
return p
def build_docx(site_root, output_path):
index_path = site_root / "index.html"
tree = lxml_html.fromstring(index_path.read_text(encoding="utf-8"))
name = clean(tree.xpath("string(//h1)"))
role = clean(tree.xpath("string(//*[contains(@class, 'role')])"))
availability = clean(tree.xpath("string(//*[contains(@class, 'availability')])"))
strengths = [clean(el.text_content()) for el in tree.xpath(".//*[contains(concat(' ', normalize-space(@class), ' '), ' strength-cloud ')]//span")]
sections = {}
for section in tree.xpath(".//section[contains(concat(' ', normalize-space(@class), ' '), ' section ')]"):
title = clean(section.xpath("string(.//*[contains(@class, 'section-title')])"))
sections[title] = section
doc = Document()
section = doc.sections[0]
section.top_margin = Inches(0.55)
section.bottom_margin = Inches(0.55)
section.left_margin = Inches(0.7)
section.right_margin = Inches(0.7)
styles = doc.styles
styles["Normal"].font.name = "Arial"
styles["Normal"].font.size = Pt(9.5)
title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
title.paragraph_format.space_after = Pt(2)
r = title.add_run(name.upper())
set_run(r, size=18, bold=True, color="111827")
subtitle = doc.add_paragraph()
subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
subtitle.paragraph_format.space_after = Pt(4)
r = subtitle.add_run(role)
set_run(r, size=10.5, bold=True, color="b91c1c")
avail = doc.add_paragraph()
avail.alignment = WD_ALIGN_PARAGRAPH.CENTER
avail.paragraph_format.space_after = Pt(6)
r = avail.add_run(availability)
set_run(r, size=8.8, color="4b5563")
contact_line = " | ".join(f"{label}: {value}" for label, value in CONTACTS.items())
contact = doc.add_paragraph()
contact.alignment = WD_ALIGN_PARAGRAPH.CENTER
contact.paragraph_format.space_after = Pt(8)
r = contact.add_run(contact_line)
set_run(r, size=7.8, color="374151")
add_bottom_border(contact, "b91c1c", "10")
profile_section = sections.get("Professional Profile")
if profile_section is not None:
add_heading(doc, "Professional Profile")
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(5)
r = p.add_run(clean(profile_section.xpath("string(.//*[contains(@class, 'profile-text')])")))
set_run(r, size=9.5, color="111827")
if strengths:
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(2)
r = p.add_run("Core strengths: ")
set_run(r, size=9, bold=True, color="111827")
r = p.add_run(", ".join(strengths))
set_run(r, size=9, color="374151")
tools_section = sections.get("Core Tools & Work Types")
if tools_section is not None:
add_heading(doc, "Core Tools & Work Types")
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(3)
r = p.add_run(clean(tools_section.xpath("string(.//*[contains(@class, 'profile-text')])")))
set_run(r, size=9.2, color="111827")
experience_section = sections.get("Professional Experience")
if experience_section is not None:
add_heading(doc, "Professional Experience")
for job in by_class(experience_section, "job-item"):
head = doc.add_paragraph()
head.paragraph_format.space_before = Pt(4)
head.paragraph_format.space_after = Pt(1)
title_text = clean(job.xpath("string(.//*[contains(@class, 'job-title')])"))
date_text = clean(job.xpath("string(.//*[contains(@class, 'job-meta')])"))
r = head.add_run(title_text)
set_run(r, size=10.2, bold=True, color="111827")
r = head.add_run(f" | {date_text}")
set_run(r, size=9, bold=True, color="b91c1c")
sub = doc.add_paragraph()
sub.paragraph_format.space_after = Pt(2)
r = sub.add_run(clean(job.xpath("string(.//*[contains(@class, 'job-sub')])")))
set_run(r, size=8.8, bold=True, color="6b7280")
for li in by_class(job, "job-desc"):
for item in li.xpath(".//li"):
add_bullet(doc, clean(item.text_content()))
education_section = sections.get("Education & Background")
if education_section is not None:
add_heading(doc, "Education & Background")
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(1)
r = p.add_run(clean(education_section.xpath("string(.//*[contains(@class, 'edu-title')])")))
set_run(r, size=9.8, bold=True, color="111827")
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(4)
r = p.add_run(clean(education_section.xpath("string(.//*[contains(@class, 'edu-sub')])")))
set_run(r, size=8.8, color="6b7280")
for paragraph in by_class(education_section, "edu-text"):
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(3)
r = p.add_run(clean(paragraph.text_content()))
set_run(r, size=9.2, color="111827")
target_section = sections.get("Career Target")
if target_section is not None:
add_heading(doc, "Career Target")
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(0)
r = p.add_run(clean(target_section.xpath("string(.//*[contains(@class, 'profile-text')])")))
set_run(r, size=9.5, color="111827")
footer = section.footer.paragraphs[0]
footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = footer.add_run("Jason Eugene Garrison | Resume | Last updated June 15, 2026")
set_run(r, size=7.5, color="6b7280")
doc.save(output_path)
if __name__ == "__main__":
root = Path(sys.argv[1]).resolve()
out = Path(sys.argv[2]).resolve()
build_docx(root, out)
+39
View File
@@ -0,0 +1,39 @@
$ErrorActionPreference = "Stop"
$siteRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).ProviderPath
$publicFiles = @(
"index.html",
"site.css",
"site.js",
"Resume_Jason_Eugene_Garrison.pdf",
"Resume_Jason_Eugene_Garrison.docx"
)
foreach ($file in $publicFiles) {
$path = Join-Path $siteRoot $file
if (Test-Path -LiteralPath $path) {
try {
icacls $path /grant "*S-1-1-0:RX" | Out-Null
} catch {
Write-Warning "Could not update public permissions for ${file}: $($_.Exception.Message)"
}
}
}
foreach ($privatePath in @(".old", "scripts", "README.md", ".htaccess")) {
$path = Join-Path $siteRoot $privatePath
if (Test-Path -LiteralPath $path) {
try {
if ($privatePath -eq ".old") {
icacls $path /remove:g "*S-1-1-0" | Out-Null
} else {
icacls $path /remove:g "*S-1-1-0" /T | Out-Null
}
} catch {
Write-Warning "Could not update private permissions for ${privatePath}: $($_.Exception.Message)"
}
}
}
Write-Host "Published file permissions updated."
+33
View File
@@ -0,0 +1,33 @@
param(
[string]$Label = "snapshot"
)
$ErrorActionPreference = "Stop"
$siteRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).ProviderPath
$oldRoot = Join-Path $siteRoot ".old"
$safeLabel = ($Label -replace "[^A-Za-z0-9._-]", "-").Trim("-")
if ([string]::IsNullOrWhiteSpace($safeLabel)) {
$safeLabel = "snapshot"
}
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$snapshot = Join-Path $oldRoot "$timestamp-$safeLabel"
New-Item -ItemType Directory -Force -Path $snapshot | Out-Null
$oldDir = Get-Item -LiteralPath $oldRoot -Force
$oldDir.Attributes = $oldDir.Attributes -bor [System.IO.FileAttributes]::Hidden
$excludeDirs = @(".git", ".old")
Get-ChildItem -LiteralPath $siteRoot -Force | Where-Object {
$excludeDirs -notcontains $_.Name
} | ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination $snapshot -Recurse -Force
}
icacls (Resolve-Path -LiteralPath $snapshot).ProviderPath /remove:g "*S-1-1-0" /T | Out-Null
Write-Host "Saved snapshot: $snapshot"
+643
View File
@@ -0,0 +1,643 @@
:root {
--primary: #b91c1c;
--text-main: #374151;
--text-muted: #6b7280;
--bg-body: #f3f4f6;
--bg-paper: #ffffff;
--bg-offset: #f9fafb;
--bg-title: #f7f8fa;
--border: #e5e7eb;
--shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
[data-theme="dark"] {
--primary: #ef4444;
--text-main: #e5e7eb;
--text-muted: #9ca3af;
--bg-body: #111827;
--bg-paper: #1f2937;
--bg-offset: #374151;
--bg-title: #263241;
--border: #4b5563;
--shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
padding: 40px 20px;
color: var(--text-main);
background: var(--bg-body);
font-family: "Roboto", Arial, sans-serif;
line-height: 1.6;
transition: background-color 0.3s, color 0.3s;
}
a {
color: inherit;
text-decoration: none;
transition: color 0.2s;
}
a:hover,
a:focus-visible {
color: var(--primary);
}
button,
a {
-webkit-tap-highlight-color: transparent;
}
.resume-paper {
max-width: 900px;
margin: 0 auto;
overflow: hidden;
background: var(--bg-paper);
border-top: 8px solid var(--primary);
box-shadow: var(--shadow);
transition: background-color 0.3s;
}
.action-bar {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 15px;
padding: 15px 40px;
background: var(--bg-offset);
border-bottom: 1px solid var(--border);
}
.btn-dl {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 38px;
padding: 8px 16px;
color: var(--text-main);
background: var(--bg-paper);
border: 1px solid var(--border);
border-radius: 4px;
font: 700 0.85rem/1 "Roboto", Arial, sans-serif;
text-transform: uppercase;
cursor: pointer;
}
.btn-dl:hover,
.btn-dl:focus-visible {
color: var(--primary);
border-color: var(--primary);
outline: none;
}
.icon-btn {
min-width: 40px;
justify-content: center;
padding: 8px;
}
.resume-header {
display: flex;
align-items: center;
gap: 40px;
padding: 50px 60px 40px;
border-bottom: 2px solid var(--border);
}
.profile-img {
width: 150px;
height: 150px;
flex: 0 0 auto;
object-fit: cover;
padding: 4px;
background: var(--bg-paper);
border: 1px solid var(--border);
border-radius: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.header-content h1 {
margin: 0 0 5px;
color: var(--text-main);
font-family: "Oswald", Arial, sans-serif;
font-size: 3rem;
line-height: 1.1;
text-transform: uppercase;
}
.role {
margin: 0 0 20px;
color: var(--primary);
font-size: 1.1rem;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
}
.availability {
margin: -8px 0 20px;
color: var(--text-muted);
font-size: 0.95rem;
font-weight: 500;
}
.contact-grid {
display: flex;
flex-wrap: wrap;
gap: 15px 25px;
margin: 0;
color: var(--text-main);
font-size: 0.9rem;
font-style: normal;
font-weight: 500;
}
.contact-item {
display: inline-flex;
align-items: center;
gap: 8px;
}
.contact-item i {
color: var(--primary);
font-size: 1rem;
}
.secure-link {
padding: 0;
color: inherit;
background: none;
border: 0;
border-bottom: 1px dotted var(--text-muted);
font: inherit;
cursor: pointer;
}
.secure-link:hover,
.secure-link:focus-visible {
color: var(--primary);
border-bottom-color: var(--primary);
outline: none;
}
.content-body {
padding: 50px 60px;
}
.section {
margin-bottom: 50px;
}
.compact-section {
margin-bottom: 42px;
}
.final-section {
margin-bottom: 0;
}
.section-title {
display: flex;
align-items: center;
margin: 0 0 28px;
padding: 10px 16px;
color: var(--text-main);
background: linear-gradient(90deg, var(--bg-title), transparent);
border-left: 5px solid var(--primary);
border-radius: 4px;
font-family: "Oswald", Arial, sans-serif;
font-size: 1.5rem;
text-transform: uppercase;
}
.profile-text {
margin: 0 0 15px;
padding-left: 20px;
color: var(--text-main);
font-size: 1.1rem;
}
.strength-cloud {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 5px 7px;
margin: 18px 0 2px 20px;
padding-top: 10px;
border-top: 1px solid var(--border);
}
.strength-cloud span {
display: inline-flex;
align-items: center;
min-height: 0;
padding: 2px 7px;
color: var(--text-main);
background: color-mix(in srgb, var(--primary) 7%, var(--bg-paper));
border: 1px solid color-mix(in srgb, var(--primary) 28%, var(--border));
border-radius: 999px;
font-size: 0.72rem;
font-weight: 700;
line-height: 1.15;
}
.strength-cloud .weight-2 {
min-height: 0;
padding: 2px 8px;
font-size: 0.75rem;
}
.strength-cloud .weight-3 {
min-height: 0;
padding: 3px 9px;
color: #ffffff;
background: var(--primary);
border-color: var(--primary);
font-size: 0.8rem;
}
.job-item {
position: relative;
margin-bottom: 35px;
padding-left: 28px;
}
.job-item::before {
position: absolute;
top: 8px;
bottom: -26px;
left: 6px;
width: 1px;
background: var(--border);
content: "";
}
.job-item::after {
position: absolute;
top: 8px;
left: 2px;
width: 9px;
height: 9px;
background: var(--primary);
border: 2px solid var(--bg-paper);
border-radius: 50%;
box-shadow: 0 0 0 1px var(--primary);
content: "";
}
.job-item:last-child::before {
display: none;
}
.job-head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 12px;
margin-bottom: 5px;
}
.job-title {
margin: 0;
color: var(--text-main);
font-size: 1.25rem;
font-weight: 700;
}
.job-meta {
margin: 0;
color: var(--primary);
font-family: "Oswald", Arial, sans-serif;
font-size: 0.95rem;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.job-sub {
margin: 0 0 10px;
color: var(--text-muted);
font-size: 0.9rem;
font-weight: 600;
}
.job-desc {
margin: 0;
padding: 0;
list-style: none;
}
.job-desc li {
position: relative;
margin-bottom: 8px;
padding-left: 20px;
color: var(--text-main);
font-size: 1rem;
}
.job-desc li::before {
position: absolute;
left: 0;
color: var(--primary);
content: "\2022";
font-size: 1.2rem;
font-weight: 700;
line-height: 1.4;
}
.edu-box {
margin-left: 20px;
padding: 30px;
background: var(--bg-offset);
border: 1px solid var(--border);
border-radius: 4px;
}
.edu-title {
margin: 0 0 5px;
color: var(--text-main);
font-size: 1.1rem;
font-weight: 700;
}
.edu-sub {
margin: 0 0 15px;
color: var(--text-muted);
font-size: 0.9rem;
font-style: italic;
}
.edu-text {
margin: 0 0 15px;
color: var(--text-main);
}
.edu-text:last-child {
margin-bottom: 0;
}
footer {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px 18px;
padding: 30px;
color: #9ca3af;
background: #111827;
font-size: 0.85rem;
letter-spacing: 1px;
text-align: center;
text-transform: uppercase;
}
@media (max-width: 800px) {
body {
padding: 0;
}
.resume-paper {
margin: 0;
border-top: 0;
box-shadow: none;
}
.action-bar {
flex-wrap: wrap;
justify-content: center;
padding: 15px 20px;
}
.resume-header {
flex-direction: column;
gap: 24px;
padding: 40px 20px;
text-align: center;
}
.header-content h1 {
font-size: 2.25rem;
}
.contact-grid {
justify-content: center;
}
.content-body {
padding: 40px 20px;
}
.job-head {
flex-direction: column;
gap: 5px;
}
.profile-text,
.job-item,
.edu-box,
.strength-cloud {
margin-left: 0;
padding-left: 0;
}
.job-item {
padding-left: 22px;
}
.job-item::before {
left: 3px;
}
.job-item::after {
left: -1px;
}
}
@media print {
@page {
size: letter;
margin: 0.45in;
}
:root {
--primary: #b91c1c;
--text-main: #000000;
--text-muted: #444444;
--bg-paper: #ffffff;
--bg-offset: #ffffff;
--border: #cccccc;
}
body {
padding: 0;
color: black;
background: white;
font-size: 9pt;
line-height: 1.35;
}
.resume-paper {
max-width: 100%;
overflow: visible;
border: 0;
border-top: 5px solid #b91c1c;
box-shadow: none;
}
.action-bar {
display: none;
}
.resume-header,
.content-body {
padding: 0;
}
.resume-header {
gap: 18px;
padding-bottom: 12px;
}
.profile-img {
width: 88px;
height: 88px;
}
.header-content h1 {
margin-bottom: 2px;
font-size: 24pt;
}
.role {
margin-bottom: 6px;
font-size: 10pt;
}
.availability {
margin-bottom: 7px;
color: #444444;
font-size: 8.4pt;
}
.contact-grid {
gap: 4px 14px;
font-size: 7.8pt;
}
.content-body {
padding-top: 12px;
}
.section,
.compact-section {
margin-bottom: 14px;
}
.section-title {
margin-bottom: 7px;
padding: 4px 8px;
background: #f7f7f7;
border-left-color: black;
border-radius: 0;
font-size: 10.5pt;
}
.profile-text {
margin-bottom: 6px;
padding-left: 10px;
font-size: 9pt;
}
.strength-cloud {
gap: 3px 5px;
margin: 8px 0 0 10px;
padding-top: 6px;
}
.strength-cloud span,
.strength-cloud .weight-2,
.strength-cloud .weight-3 {
padding: 1px 5px;
color: black;
background: white;
border-color: #cccccc;
font-size: 7.5pt;
}
.job-item {
margin-bottom: 10px;
padding-left: 18px;
break-inside: avoid;
}
.job-item::before {
bottom: -8px;
left: 3px;
}
.job-item::after {
left: -1px;
background: #b91c1c;
border-color: white;
}
.job-head {
margin-bottom: 1px;
}
.job-title {
font-size: 9.7pt;
}
.job-meta,
.job-sub {
font-size: 8.2pt;
}
.job-desc li {
margin-bottom: 2px;
padding-left: 14px;
font-size: 8.7pt;
}
.job-desc li::before {
font-size: 9pt;
line-height: 1.25;
}
.edu-box {
margin-left: 10px;
padding: 10px;
background: white;
}
.edu-title {
font-size: 9.2pt;
}
.edu-sub,
.edu-text {
font-size: 8.7pt;
}
.strength-cloud {
break-inside: avoid;
}
a {
color: black;
text-decoration: none;
}
.job-meta {
color: black;
}
.secure-link {
color: black;
border: 0;
}
}
+104
View File
@@ -0,0 +1,104 @@
(function () {
const root = document.documentElement;
const toggleButton = document.getElementById("theme-toggle");
const toggleIcon = toggleButton ? toggleButton.querySelector("i") : null;
const storage = {
get(key) {
try {
return window.localStorage.getItem(key);
} catch (error) {
return null;
}
},
set(key, value) {
try {
window.localStorage.setItem(key, value);
} catch (error) {
return false;
}
return true;
}
};
const setTheme = (theme) => {
const isDark = theme === "dark";
if (isDark) {
root.setAttribute("data-theme", "dark");
} else {
root.removeAttribute("data-theme");
}
if (toggleIcon) {
toggleIcon.classList.toggle("fa-sun", isDark);
toggleIcon.classList.toggle("fa-moon", !isDark);
}
if (toggleButton) {
toggleButton.setAttribute("aria-pressed", String(isDark));
toggleButton.title = isDark ? "Switch to light theme" : "Switch to dark theme";
}
};
const savedTheme = storage.get("theme") || "light";
setTheme(savedTheme);
if (toggleButton) {
toggleButton.addEventListener("click", () => {
const nextTheme = root.hasAttribute("data-theme") ? "light" : "dark";
setTheme(nextTheme);
storage.set("theme", nextTheme);
});
}
const printButton = document.getElementById("print-resume");
if (printButton) {
printButton.addEventListener("click", () => window.print());
}
const contactKey = 43;
const contactValues = {
email: [65, 74, 88, 68, 69, 78, 94, 76, 78, 69, 78, 76, 74, 89, 89, 66, 88, 68, 69, 107, 66, 72, 71, 68, 94, 79, 5, 72, 68, 70],
ky: [3, 25, 28, 27, 2, 11, 19, 19, 18, 6, 19, 27, 28, 25],
tn: [3, 19, 29, 30, 2, 11, 18, 19, 30, 6, 26, 29, 31, 26]
};
const revealContact = (contactType) => {
const encoded = contactValues[contactType];
if (!encoded) {
return "";
}
return encoded.map((code) => String.fromCharCode(code ^ contactKey)).join("");
};
const revealButtonContact = (button) => {
const contactType = button.dataset.contact;
const value = revealContact(contactType);
if (!value) {
return;
}
const href = contactType === "email"
? `mailto:${value}`
: `tel:${value.replace(/\D/g, "")}`;
const link = document.createElement("a");
link.href = href;
link.rel = "nofollow";
link.textContent = value;
button.replaceWith(link);
};
document.querySelectorAll("[data-contact]").forEach((button) => {
button.addEventListener("click", () => revealButtonContact(button));
});
window.addEventListener("beforeprint", () => {
document.querySelectorAll("[data-contact]").forEach(revealButtonContact);
});
})();