openvibers/afresume Commits

Revamp: fix invisible hero title on Steam Deck, tighten copy sitewide, clean junk files, move to OpenVibers

OP OpenVibers committed 16 Jul 2026 · 6ec7c2202c · 25 files, +76 −998

- Hero title: drop background-clip:text gradient (invisible on Steam Deck
  1280x800 — same paint bug as the old ImpactMetrics fix); solid color +
  safer clamp() sizing
- Hero: tighter lead, 5 chips instead of 7, 6 rotator roles instead of 9
- Profile: 3 paragraphs condensed, Seeking list trimmed to 3 groups
- Impact metrics: shorter labels
- Experience: all bullets tightened (OpenVibe entry heavily condensed)
- Projects: punchier summaries; OpenVibe + HoboQuest detail pages condensed
- GitHub profile links + schema.org sameAs now point to github.com/OpenVibers
- Removed 8 superseded resume PDFs/DOCX and 2 duplicate build scripts

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Changes

ddiff --git a/build_resume_v12.py b/build_resume_v12.py
ddeleted file mode 100755
iindex ec0df15..0000000
---- a/build_resume_v12.py
++++ /dev/null
@@@ -1,299 +0,0 @@
--from reportlab.pdfbase import pdfmetrics
--from reportlab.pdfbase.ttfonts import TTFont
--from reportlab.lib.pagesizes import letter
--from reportlab.lib.colors import HexColor, white
--from reportlab.pdfgen import canvas
--from reportlab.platypus import Paragraph
--from reportlab.lib.styles import ParagraphStyle
--
--pdfmetrics.registerFont(TTFont('FA', 'fontawesome-webfont.ttf'))
--
--PAGE_W, PAGE_H = letter
--
--# Palette
--BG = HexColor('#eef2f7')
--BLUE = HexColor('#4f84d6')
--BLUE_DARK = HexColor('#2a4f90')
--BLUE_LINE = HexColor('#7ea8ee')
--BLUE_SOFT = HexColor('#5f92de')
--TEXT = HexColor('#34548f')
--SHADOW = HexColor('#d9dee8')
--CARD_BG = HexColor('#f8fbff')
--
--# Typography
--BODY = ParagraphStyle('Body', fontName='Helvetica', fontSize=10.4, leading=12.1, textColor=TEXT, spaceAfter=0)
--COMPANY = ParagraphStyle('Company', fontName='Helvetica-Bold', fontSize=9.8, leading=11, textColor=BLUE)
--BULLET = ParagraphStyle('Bullet', fontName='Helvetica', fontSize=9.15, leading=10.25, textColor=TEXT)
--CARD_TITLE = ParagraphStyle('CardTitle', fontName='Helvetica-Bold', fontSize=10.9, leading=12.2, textColor=BLUE_DARK)
--CARD_DESC = ParagraphStyle('CardDesc', fontName='Helvetica', fontSize=8.35, leading=9.15, textColor=TEXT)
--PRODUCT_TITLE = ParagraphStyle('ProductTitle', fontName='Helvetica-Bold', fontSize=10.2, leading=11.4, textColor=BLUE_DARK)
--URL_STYLE = ParagraphStyle('URL', fontName='Helvetica', fontSize=8.7, leading=9.4, textColor=BLUE)
--LEARN = ParagraphStyle('Learn', fontName='Helvetica', fontSize=8.7, leading=9.8, textColor=TEXT)
--EXP_TITLE = ParagraphStyle('ExpTitle', fontName='Helvetica-Bold', fontSize=14.6, leading=16.0, textColor=BLUE_DARK)
--
--products = [
-- ('\uf0ac','HoboTools','hobo.tools','Shared-account ecosystem and network hub','https://hobo.tools'),
-- ('\uf03d','HoboStreamer','hobostreamer.com','Streaming, clips, VODs, chat, creator tools','https://hobostreamer.com'),
-- ('\uf041','HoboMaps','maps.hobo.tools','Map overlays, research, and survival data','https://maps.hobo.tools'),
-- ('\uf11b','HoboQuest','hobo.quest','Browser MMORPG, multiplayer games, live canvas','https://hobo.quest'),
--]
--
--core_cards = [
-- ('\uf201','Analytics / Data','SQL, Excel, reporting, dashboards, defect research, integrity, and root-cause analysis'),
-- ('\uf121','Engineering / Web','JavaScript, TypeScript, Node.js, Python, PHP, React, Vue, Electron, HTML5, CSS/SASS, REST APIs'),
-- ('\uf1c0','Systems / Infrastructure','Linux, Windows Server, NGINX, Apache, MongoDB, Redis, Cloudflare, deployment, scaling, and DDoS resilience'),
-- ('\uf11b','Game Dev / Product','Lua, C#, C++, gameplay systems, creator tooling, UX, graphics, and product thinking'),
--]
--
--
--def round_rect(c, x, y, w, h, r, fill, stroke=BLUE_LINE, stroke_width=1, shadow=True, shadow_dx=4, shadow_dy=-4):
-- if shadow:
-- c.setFillColor(SHADOW)
-- c.setStrokeColor(SHADOW)
-- c.roundRect(x+shadow_dx, y+shadow_dy, w, h, r, fill=1, stroke=0)
-- c.setFillColor(fill)
-- c.setStrokeColor(stroke)
-- c.setLineWidth(stroke_width)
-- c.roundRect(x, y, w, h, r, fill=1, stroke=1)
--
--
--def para(c, html, style, x, top, width, height=200):
-- p = Paragraph(html, style)
-- _, h = p.wrap(width, height)
-- p.drawOn(c, x, top-h)
-- return h
--
--
--def draw_icon_circle(c, x, y, size, glyph, fg=BLUE, bg=CARD_BG, font_size=15):
-- c.setFillColor(bg)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(1.1)
-- c.circle(x + size/2, y + size/2, size/2, fill=1, stroke=1)
-- c.setFont('FA', font_size)
-- c.setFillColor(fg)
-- c.drawCentredString(x + size/2, y + size/2 - font_size*0.34, glyph)
--
--
--def section_header(c, x, y, w, text, glyph):
-- round_rect(c, x, y, w, 30, 15, BLUE, stroke=BLUE, stroke_width=0.8, shadow=True)
-- draw_icon_circle(c, x+10, y+4, 22, glyph, fg=white, bg=BLUE_SOFT, font_size=12)
-- c.setFont('Helvetica-Bold', 15)
-- c.setFillColor(white)
-- c.drawString(x+38, y+9, text)
--
--
--def chip_width(text):
-- return pdfmetrics.stringWidth(text, 'Helvetica-Bold', 8.9) + 28
--
--
--def draw_chip(c, x, y, text, glyph, h=22):
-- w = chip_width(text)
-- c.setFillColor(BLUE_SOFT)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.8)
-- c.roundRect(x, y, w, h, h/2, fill=1, stroke=1)
-- c.setFont('FA', 10)
-- c.setFillColor(white)
-- c.drawString(x+9, y+6, glyph)
-- c.setFont('Helvetica-Bold', 8.9)
-- c.drawString(x+22, y+7, text)
-- return w
--
--
--def draw_header(c, x, y, w, h):
-- round_rect(c, x, y, w, h, 28, BLUE, stroke=BLUE, stroke_width=0.8, shadow=True)
-- c.setFillColor(white)
-- c.setFont('Helvetica-Bold', 31)
-- c.drawString(x+20, y+h-46, 'Alex Frison')
--
-- subtitle_style = ParagraphStyle('subtitle', fontName='Helvetica-Bold', fontSize=10.3, leading=11.0, textColor=white)
-- subtitle = ('Data analyst and systems-minded builder across analytics, robotics support, retail sales leadership, and full-stack platforms.')
-- para(c, subtitle, subtitle_style, x+22, y+h-74, 330, 40)
--
-- # Chips - always inside the header; fixed 2-row layout
-- row1 = [('\uf201','Analytics / BI'),('\uf121','Full Stack Eng'),('\uf0ad','Robotics / RME')]
-- row2 = [('\uf07a','Sales Leadership'),('\uf0b1','Product Builder')]
-- cx = x + 22
-- for glyph, text in row1:
-- cx += draw_chip(c, cx, y+42, text, glyph) + 10
-- cx = x + 22
-- for glyph, text in row2:
-- cx += draw_chip(c, cx, y+14, text, glyph) + 10
--
-- # contact panel
-- cp_w, cp_h = 206, 84
-- cp_x, cp_y = x + w - cp_w - 18, y + h - cp_h - 18
-- round_rect(c, cp_x, cp_y, cp_w, cp_h, 18, BLUE_SOFT, stroke=BLUE_LINE, stroke_width=0.9, shadow=False)
-- top_icon_y = cp_y + cp_h - 36
-- bottom_icon_y = cp_y + 12
-- draw_icon_circle(c, cp_x+12, top_icon_y, 24, '\uf095', fg=white, bg=BLUE_SOFT, font_size=12)
-- draw_icon_circle(c, cp_x+12, bottom_icon_y, 24, '\uf0e0', fg=white, bg=BLUE_SOFT, font_size=11)
-- c.setStrokeColor(HexColor('#83a8e6'))
-- c.setLineWidth(0.8)
-- c.line(cp_x+18, cp_y + cp_h/2, cp_x+cp_w-18, cp_y + cp_h/2)
-- c.setFillColor(white)
-- c.setFont('Helvetica-Bold', 10.5)
-- c.drawString(cp_x+42, cp_y + cp_h - 20, '+1 (425) 367-3997')
-- c.drawString(cp_x+42, cp_y + 28, '[email protected]')
--
--
--def top_feature_card(c, x, y, w, h, glyph, title_html, bullets, desc_size=8.45):
-- round_rect(c, x, y, w, h, 18, CARD_BG, shadow=True)
-- draw_icon_circle(c, x+14, y+h-46, 28, glyph, fg=BLUE, bg=CARD_BG, font_size=14)
-- para(c, title_html, ParagraphStyle('ft', parent=CARD_TITLE, fontSize=11.2, leading=12.5), x+52, y+h-14, w-66, 26)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.8)
-- c.line(x+18, y+h-52, x+w-18, y+h-52)
-- html = '<br/>'.join(f'&bull; {b}' for b in bullets)
-- para(c, html, ParagraphStyle('fb', parent=CARD_DESC, fontSize=desc_size, leading=9.1), x+18, y+h-60, w-36, 60)
--
--
--def product_card(c, x, y, w, h, glyph, title, url, desc, link):
-- round_rect(c, x, y, w, h, 16, CARD_BG, shadow=True)
-- draw_icon_circle(c, x+12, y+h-16-22, 22, glyph, fg=BLUE, bg=CARD_BG, font_size=11)
-- tx = x + 42
-- para(c, title, ParagraphStyle('pt', parent=PRODUCT_TITLE, fontSize=10.1, leading=10.9), tx, y+h-10, w-(tx-x)-12, 18)
-- para(c, f'<u>{url}</u>', ParagraphStyle('pu', parent=URL_STYLE, fontSize=8.6, leading=8.9), tx, y+h-30, w-(tx-x)-12, 14)
-- para(c, desc, ParagraphStyle('pd', parent=CARD_DESC, fontSize=8.4, leading=8.8), tx, y+18, w-(tx-x)-12, 24)
-- c.linkURL(link, (x, y, x+w, y+h), relative=0, thickness=0)
--
--
--def core_card(c, x, y, w, h, glyph, title, desc):
-- round_rect(c, x, y, w, h, 16, CARD_BG, shadow=True)
-- draw_icon_circle(c, x+12, y+h-14-22, 22, glyph, fg=BLUE, bg=CARD_BG, font_size=12)
-- para(c, title, ParagraphStyle('ct2', parent=CARD_TITLE, fontSize=10.8, leading=11.4), x+42, y+h-10, w-54, 18)
-- # More left margin and slightly lower top to separate from title.
-- para(c, desc, ParagraphStyle('cd2', parent=CARD_DESC, fontSize=8.45, leading=9.0), x+58, y+30, w-70, 24)
--
--
--def draw_experience(c, x, y_top, title, date, company, bullets, rule_gap=10):
-- title_style = ParagraphStyle('et', parent=EXP_TITLE, fontSize=14.6, leading=16.0)
-- title_w = PAGE_W - x - 34 - 132
-- h = para(c, title, title_style, x, y_top, title_w, 24)
-- c.setFont('Helvetica-Bold', 11.8)
-- c.setFillColor(BLUE)
-- c.drawRightString(PAGE_W-34, y_top-1, date)
-- y = y_top - max(h, 14) - 7
-- ch = para(c, company, COMPANY, x, y, PAGE_W-x-34, 14)
-- y -= ch + 3
-- bullet_html = '<br/>'.join(f'&bull; {b}' for b in bullets)
-- bh = para(c, bullet_html, BULLET, x+10, y, PAGE_W-x-44, 60)
-- bottom = y - bh - rule_gap
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.8)
-- c.line(x, bottom, PAGE_W-34, bottom)
-- return bottom - 14
--
--
--def build(path):
-- c = canvas.Canvas(path, pagesize=letter)
-- c.setTitle('Alex Frison Resume')
--
-- for page in [1, 2]:
-- c.setFillColor(BG)
-- c.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
-- x = 18
-- if page == 1:
-- header_h = 170
-- header_y = PAGE_H - 18 - header_h
-- draw_header(c, x, header_y, PAGE_W-36, header_h)
--
-- # top feature cards, tighter to header to reduce blank band
-- top_y = header_y - 8 - 126
-- gap = 14
-- card_w = (PAGE_W - 36 - gap*3) / 4
-- card_h = 126
-- xs = [x, x+card_w+gap, x+(card_w+gap)*2, x+(card_w+gap)*3]
-- top_feature_card(c, xs[0], top_y, card_w, card_h, '\uf201', 'Analytics /<br/>Data', [
-- 'SQL, Excel, Inventory Control / Quality Assurance reporting',
-- 'Defect review and discrepancy analysis',
-- 'Live dashboards and analytics'
-- ])
-- top_feature_card(c, xs[1], top_y, card_w, card_h, '\uf0ad', 'RME /<br/>Robotics', [
-- 'Amazon Robotics FC launch and automation',
-- 'Robotics support and troubleshooting',
-- 'Electro-mech hardware exposure'
-- ])
-- top_feature_card(c, xs[2], top_y, card_w, card_h, '\uf07a', 'Sales /<br/>Leadership', [
-- 'Plans, devices, financing',
-- 'Coaching, meetings, escalations',
-- 'Retention and quota execution'
-- ])
-- top_feature_card(c, xs[3], top_y, card_w, card_h, '\uf03d', 'Product /<br/>Streaming', [
-- 'HoboTools, HoboStreamer, HoboQuest',
-- 'Clips, VODs, creator tools',
-- 'Cross-service UX and operations'
-- ], desc_size=8.0)
--
-- profile_header_y = top_y - 22 - 16
-- section_header(c, x, profile_header_y, PAGE_W-36, 'PROFILE', '\uf2c0')
-- profile_box_y = profile_header_y - 50
-- round_rect(c, x, profile_box_y-4, PAGE_W-36, 46, 18, CARD_BG, shadow=False)
-- profile = ('Versatile data analyst and systems-minded builder with experience across Amazon ICQA analytics, Amazon RME support, retail sales leadership, and full-stack product development. Strong at turning operational context into clear reporting, stakeholder-ready analysis, practical tools, and process improvements.')
-- para(c, profile, BODY, x+18, profile_box_y+38, PAGE_W-72, 30)
--
-- exp_header_y = profile_box_y - 38
-- section_header(c, x, exp_header_y, PAGE_W-36, 'EXPERIENCE', '\uf0b1')
-- cur_y = exp_header_y - 16
-- cur_y = draw_experience(c, x+18, cur_y, 'Data Analyst', '2024 - Present', 'Amazon', [
-- 'Built ICQA SQL and Excel reporting for inventory accuracy, discrepancy review, and defect visibility.',
-- 'Turned mismatch findings into dashboards, recurring reports, and stakeholder-ready analysis for operational decisions.'
-- ])
-- cur_y = draw_experience(c, x+18, cur_y, 'Reliability, Maintenance, and Engineering', '2023 - 2024', 'Amazon', [
-- 'Supported launch-stage ARS robotics and conveyance operations while monitoring automation flow, floor conditions, and equipment behavior.',
-- 'Assisted troubleshooting across robotics, uptime, safety, and electro-mechanical issues in a high-change launch environment.'
-- ])
-- draw_experience(c, x+18, cur_y, 'Assistant Manager / Sales Representative', '2020 - 2023', 'Verizon Wireless', [
-- 'Sold smartphones, wireless plans, accessories, protection, financing, and credit-card offers in a quota-driven retail environment; earned district-level recognition.',
-- 'Coached associates, ran monthly meetings and workshops, and improved customer service, retention, merchandising, and inventory ownership.'
-- ], rule_gap=8)
-- else:
-- y = PAGE_H - 26
-- y = draw_experience(c, x, y, 'Founder / Product Engineer / Streaming & Game Builder', 'Present', 'Hobo Tools / HoboStreamer / HoboQuest / HoboMaps', [
-- 'Built shared-account web products across Hobo Tools, HoboStreamer, HoboQuest, and HoboMaps with unified UX, connected identity, and cross-service navigation.',
-- 'Developed creator-facing streaming systems including clips, VODs, chat, moderation, analytics, restream support, and multi-path delivery; also built browser multiplayer game experiences and creator tools using Lua, C#, C++, gameplay systems, and product-minded UX.'
-- ])
-- y = draw_experience(c, x, y, 'Founder / Developer / Systems Admin', '2015 - Present', 'GameServerStats, LLC', [
-- 'Built analytics-focused web platforms that track 200,000+ live game servers using JavaScript, Node.js, PHP, Python, SQL, MongoDB, Redis, HTML, and CSS.',
-- 'Designed ingestion, search, dashboards, Linux infrastructure, scaling, DDoS resilience, support workflows, and business reporting.'
-- ])
-- y = draw_experience(c, x, y, 'Earlier Technical and Business Experience', '2012 - Present', 'Devolved, GModStore.com, eBay', [
-- 'Built multiplayer game systems, digital products, graphics, support docs, e-commerce listings, moderation workflows, and monetized online communities.'
-- ], rule_gap=6)
--
-- prod_header_y = y - 16
-- section_header(c, x, prod_header_y, PAGE_W-36, 'SELECTED PRODUCTS / LIVE EXAMPLES', '\uf0c1')
-- pw = (PAGE_W - 36 - 18) / 2
-- ph = 66
-- py1 = prod_header_y - 72
-- xs = [x, x+pw+18]
-- product_card(c, xs[0], py1, pw, ph, *products[0])
-- product_card(c, xs[1], py1, pw, ph, *products[1])
-- py2 = py1 - 76
-- product_card(c, xs[0], py2, pw, ph, *products[2])
-- product_card(c, xs[1], py2, pw, ph, *products[3])
--
-- core_header_y = py2 - 28
-- section_header(c, x, core_header_y, PAGE_W-36, 'CORE TECHNICAL STACK', '\uf1c0')
-- cw = (PAGE_W - 36 - 18) / 2
-- ch = 58
-- cy1 = core_header_y - 64
-- core_card(c, x, cy1, cw, ch, core_cards[0][0], core_cards[0][1], core_cards[0][2])
-- core_card(c, x+cw+18, cy1, cw, ch, core_cards[1][0], core_cards[1][1], core_cards[1][2])
-- cy2 = cy1 - 66
-- core_card(c, x, cy2, cw, ch, core_cards[2][0], core_cards[2][1], core_cards[2][2])
-- core_card(c, x+cw+18, cy2, cw, ch, core_cards[3][0], core_cards[3][1], core_cards[3][2])
--
-- learn_header_y = cy2 - 28
-- section_header(c, x, learn_header_y, PAGE_W-36, 'EDUCATION / LEARNING', '\uf19d')
-- learn_box_y = learn_header_y - 48
-- round_rect(c, x, learn_box_y, PAGE_W-36, 40, 16, CARD_BG, shadow=False)
-- learn_text = ('Snohomish High School, WA - AP Statistics, Calculus, English, Creative Writing, Spanish - Self-taught systems builder with a strong self-starter mindset, focused on analytics, infrastructure, graphics, photography, and practical problem solving.')
-- para(c, learn_text, LEARN, x+16, learn_box_y+27, PAGE_W-68, 20)
--
-- c.showPage()
-- c.save()
--
--
--if __name__ == '__main__':
-- build('Alex_Frison_Resume.pdf')
ddiff --git a/build_resume_v13 (copy).py b/build_resume_v13 (copy).py
ddeleted file mode 100644
iindex b19808c..0000000
---- a/build_resume_v13 (copy).py
++++ /dev/null
@@@ -1,593 +0,0 @@
--"""
--build_resume_v13.py — Alex Frison resume (2026 edition).
--
--Improvements over v12:
--- Pulls the rich detail that lives on alexfrison.net (SOX/IRDR work,
-- Anthropic SQL copilot, autonomous root-cause bot, OpenVibe 15-microservice
-- kernel, HoboQuest custom engine, GameServerStats LLC scale, Verizon
-- district-level recognition).
--- New "Impact at a glance" metrics strip on page 1 (recruiter-skim friendly).
--- Tighter typography, denser content, fewer wasted bands of blank space.
--- Page 1 = pitch + first three roles (recent + revenue-relevant).
--- Page 2 = remaining roles, six featured projects with live URLs, full
-- technical stack, education + links footer.
--- Same palette so it stays visually consistent with the site.
--
--Run:
-- cd /opt/repos/alexfrison.net && python build_resume_v13.py
--Outputs:
-- Alex_Frison_Resume.pdf (next to the script)
--"""
--
--from reportlab.pdfbase import pdfmetrics
--from reportlab.pdfbase.ttfonts import TTFont
--from reportlab.lib.pagesizes import letter
--from reportlab.lib.colors import HexColor, white
--from reportlab.pdfgen import canvas
--from reportlab.platypus import Paragraph
--from reportlab.lib.styles import ParagraphStyle
--
--pdfmetrics.registerFont(TTFont('FA', 'fontawesome-webfont.ttf'))
--
--PAGE_W, PAGE_H = letter
--
--# --- palette ----------------------------------------------------------------
--BG = HexColor('#eef2f7')
--BLUE = HexColor('#4f84d6')
--BLUE_DARK = HexColor('#2a4f90')
--BLUE_LINE = HexColor('#7ea8ee')
--BLUE_SOFT = HexColor('#5f92de')
--TEXT = HexColor('#34548f')
--SUBTLE = HexColor('#5a73a8')
--SHADOW = HexColor('#d9dee8')
--CARD_BG = HexColor('#f8fbff')
--ACCENT_GOLD = HexColor('#c98b1f') # used only for metric numerals
--
--# --- styles -----------------------------------------------------------------
--BODY = ParagraphStyle('Body', fontName='Helvetica', fontSize=9.6, leading=11.4,
-- textColor=TEXT, spaceAfter=0)
--COMPANY = ParagraphStyle('Company', fontName='Helvetica-Bold', fontSize=9.6,
-- leading=11, textColor=BLUE)
--LOCATION = ParagraphStyle('Location', fontName='Helvetica-Oblique', fontSize=8.6,
-- leading=10, textColor=SUBTLE)
--BULLET = ParagraphStyle('Bullet', fontName='Helvetica', fontSize=8.6,
-- leading=10.0, textColor=TEXT)
--STACK = ParagraphStyle('Stack', fontName='Helvetica-Oblique', fontSize=8.2,
-- leading=10, textColor=SUBTLE)
--CARD_TITLE = ParagraphStyle('CardTitle', fontName='Helvetica-Bold', fontSize=10.7,
-- leading=12, textColor=BLUE_DARK)
--CARD_DESC = ParagraphStyle('CardDesc', fontName='Helvetica', fontSize=8.35,
-- leading=9.6, textColor=TEXT)
--PRODUCT_TITLE = ParagraphStyle('ProductTitle', fontName='Helvetica-Bold',
-- fontSize=10.0, leading=11.2, textColor=BLUE_DARK)
--URL_STYLE = ParagraphStyle('URL', fontName='Helvetica', fontSize=8.4,
-- leading=9.2, textColor=BLUE)
--LEARN = ParagraphStyle('Learn', fontName='Helvetica', fontSize=8.7,
-- leading=10, textColor=TEXT)
--EXP_TITLE = ParagraphStyle('ExpTitle', fontName='Helvetica-Bold', fontSize=13.4,
-- leading=15.4, textColor=BLUE_DARK)
--METRIC_NUM = ParagraphStyle('MetricNum', fontName='Helvetica-Bold', fontSize=22,
-- leading=24, textColor=BLUE_DARK, alignment=1)
--METRIC_LBL = ParagraphStyle('MetricLbl', fontName='Helvetica', fontSize=7.8,
-- leading=9.2, textColor=TEXT, alignment=1)
--
--# --- content ---------------------------------------------------------------
--PROFILE = (
-- "Data analyst and systems architect with 10+ years building and shipping "
-- "production systems. <b>1,000+ SOX-compliance IRDRs personally researched "
-- "at Amazon ICQA</b>; built an Anthropic SQL copilot and autonomous "
-- "root-cause-analysis agent in daily use. Solo architect of a "
-- "<b>15-microservice open-source platform kernel</b> (OpenVibe), a "
-- "live-streaming stack (HoboStreamer), and a 14-service identity network. "
-- "Previously <b>district top performer &rarr; Assistant Manager at Verizon "
-- "Wireless</b>, coaching 4&ndash;8 reps."
--)
--
--METRICS = [
-- ('1,000+', 'SOX IRDRs hand-researched', '\uf002'),
-- ('15', 'OpenVibe microservices shipped', '\uf233'),
-- ('200K+', 'Live game servers tracked', '\uf11b'),
-- ('20+', 'Amazon TamperMonkey/<br/>Python scripts','\uf085'),
-- ('10+', 'Years running my own LLC', '\uf0b1'),
--]
--
--TOP_FEATURE_CARDS = [
-- ('\uf201', 'Analytics /<br/>Data', [
-- 'SQL, Power Query, Excel, dashboards',
-- 'SOX-compliant IRDR research + LLM agents',
-- ]),
-- ('\uf085', 'Robotics /<br/>RME &amp; AFM', [
-- 'Amazon Robotics (ARS) floor monitor',
-- 'Pod / drive / workstation troubleshooting',
-- ]),
-- ('\uf07a', 'Sales /<br/>Leadership', [
-- 'Top performer; district-level recognition',
-- 'Assistant Manager; coached 4&ndash;8 reps',
-- ]),
-- ('\uf03d', 'Product /<br/>Engineering', [
-- 'OpenVibe: 15-service platform kernel',
-- 'HoboStreamer, HoboQuest, Hobo Tools',
-- ]),
--]
--
--EXPERIENCE = [
-- {
-- 'title': 'Data Analyst — ICQA',
-- 'date': '2024 - 2025',
-- 'company': 'Amazon · PAE2, Arlington WA',
-- 'bullets': [
-- 'Own SOX-compliance IRDR workflow &mdash; <b>hand-researched 1,000+ inventory mismatches</b>, authored reporting satisfying Sarbanes-Oxley audit trails.',
-- 'Authored an Anthropic-powered <b>SQL copilot</b> + an <b>autonomous root-cause-analysis agent</b> that plans, gathers evidence, and writes complete IRDRs end-to-end &mdash; collapsing hours of manual research per defect into seconds.',
-- ],
-- 'stack': 'SQL · Excel / Power Query · Python · Anthropic API · TamperMonkey · SOX Compliance',
-- },
-- {
-- 'title': 'Amnesty Floor Monitor (AFM) — Amazon Robotics',
-- 'date': '2023 - 2024',
-- 'company': 'Amazon · Robotics launch site',
-- 'bullets': [
-- 'Monitored Amazon Robotics (ARS) automation during launch-stage operations &mdash; enforced amnesty zones, cleared obstructions, kept pod and drive-unit flow safe.',
-- 'Investigated recurring pod, drive, and workstation defects on the floor; documented patterns and escalated to RME and ops leadership to drive process improvements.',
-- ],
-- 'stack': 'Amazon Robotics · AFM protocols · Floor safety · Defect documentation',
-- },
-- {
-- 'title': 'Assistant Manager / Sales Representative',
-- 'date': '2020 - 2023',
-- 'company': 'Verizon Wireless',
-- 'bullets': [
-- 'Consistently ranked among <b>top performers in the district</b> &mdash; hit and exceeded monthly quotas across smartphones, plans, accessories, device protection, financing, and Verizon Visa offers.',
-- 'Promoted to Assistant Manager; mentored and coached a team of 4&ndash;8 reps through weekly 1-on-1s, floor coaching, and monthly workshops on objection handling, upselling, and customer retention. Led store opens / closes, inventory, escalations, and daily huddles.',
-- ],
-- 'stack': 'Consultative Sales · Team Leadership · Coaching · Retail Ops · Quota Attainment · Customer Retention',
-- },
-- {
-- 'title': 'Founder · Systems Architect · Principal Engineer',
-- 'date': '2022 - Present',
-- 'company': 'Hobo Network / OpenVibe',
-- 'bullets': [
-- 'Sole architect and engineer on <b>OpenVibe</b> &mdash; a 15-microservice open-source platform kernel: distributed event bus, OIDC/OAuth2 identity, media object store, live ingest + restream, real-time chat, billing ledger, AI/search backbone. Self-hosted alternative to Twitch + YouTube + Patreon + Discord. 16 phases documented, 11+ shipped.',
-- '<b>HoboStreamer</b>: live-streaming platform from scratch &mdash; WebRTC SFU, HLS, multi-destination RTMP restream (OBS &rarr; Twitch / YouTube / Kick), auto-recorded VODs, clip detection, chat, voice channels, analytics, moderation, and an embedded in-stream browser game. Fully open source.',
-- '<b>HoboQuest</b> (browser MMORPG) + <b>hobo.tools</b> (SSO + OAuth2 across 14 services): custom isomorphic engine with Source-Engine-style client prediction, 4 live game instances; shared themes, notifications, cross-domain sessions.',
-- ],
-- 'stack': 'TypeScript · Node.js · Express · PostgreSQL / SQLite · Redis · WebRTC · WebSockets · OAuth2 / OIDC · FFmpeg · Lua · C#',
-- },
-- {
-- 'title': 'Founder · Developer · Systems Admin',
-- 'date': '2015 - Present',
-- 'company': 'GameServerStats, LLC',
-- 'bullets': [
-- 'Built analytics platforms tracking <b>200,000+ live game servers</b>; #1 Google result for several major titles, licensed to third-party sites under written agreement.',
-- 'Self-built and collocated 1U / 2U servers in datacenters; designed ingestion, dashboards, DDoS resilience, and ran the entire LLC &mdash; licenses, taxes, partner relations &mdash; for 10+ years.',
-- ],
-- 'stack': 'Node.js · PHP · Python · MongoDB · MySQL · Redis · NGINX · Linux',
-- },
-- {
-- 'title': 'Earlier Technical &amp; Business Experience',
-- 'date': '2012 - 2015',
-- 'company': 'Devolved · GModStore · eBay',
-- 'bullets': [
-- 'Built multiplayer game systems, in-game economies, and digital products sold through ScriptFodder / GModStore.',
-- 'Created graphics, support docs, e-commerce listings, moderation workflows; ran online communities at scale.',
-- ],
-- 'stack': 'Lua · PHP · MySQL · Photoshop · Source Engine',
-- },
--]
--
--PROJECTS = [
-- ('\uf135', 'OpenVibe',
-- 'github.com/HoboStreamer/OpenVibe-Temporary-MonoRepo',
-- '15-microservice open-source platform kernel &mdash; identity, event bus, media store, live ingest, billing, AI/search. Sole architect across 16 phases.',
-- 'https://github.com/HoboStreamer/OpenVibe-Temporary-MonoRepo'),
-- ('\uf0eb', 'Autonomous IRDR Root-Cause Bot',
-- 'Internal Amazon tool',
-- 'Autonomous agent that plans, gathers evidence, and files SOX-compliant IRDRs end-to-end &mdash; built on 1,000+ hand-researched reports.',
-- 'https://alexfrison.net/portfolio'),
-- ('\uf1c0', 'Amazon SQL Copilot',
-- 'Internal Amazon tool',
-- 'Anthropic-powered SQL assistant that auto-discovers tables / FKs / semantics and streams runnable, table-aware SQL for ICQA analysts.',
-- 'https://alexfrison.net/portfolio'),
-- ('\uf03d', 'HoboStreamer',
-- 'hobostreamer.com',
-- 'Live streaming platform from scratch &mdash; WebRTC + HLS, multi-RTMP restream, VODs, clips, chat, voice, analytics. 100% open source.',
-- 'https://hobostreamer.com'),
--]
--
--CORE_STACK = [
-- ('\uf201', 'Analytics / Data',
-- 'SQL · Excel + Power Query + VBA · Python (pandas) · Dashboards · ICQA reporting · Root-cause analysis · Anthropic / LLM tool-use agents'),
-- ('\uf121', 'Engineering / Web',
-- 'JavaScript / TypeScript · Node.js · Express · Python · PHP · React · Vue · REST APIs · HTML5 / CSS · WebSockets / WebRTC · OAuth2 / OIDC'),
-- ('\uf233', 'Systems / Infrastructure',
-- 'Linux · NGINX · PostgreSQL / MySQL / MongoDB / Redis · Cloudflare · DDoS resilience · Self-built 1U / 2U servers'),
-- ('\uf085', 'Robotics · Game Dev · Leadership',
-- 'ARS robotics support · Electro-mechanical troubleshooting · Lua / C# / C++ · Source Engine · Team coaching'),
--]
--
--LINKS = [
-- ('\uf0ac', 'alexfrison.net', 'https://alexfrison.net'),
-- ('\uf0e0', '[email protected]', 'mailto:[email protected]'),
-- ('\uf095', '(425) 367-3997', 'tel:+14253673997'),
-- ('\uf08c', 'linkedin.com/in/fris', 'https://www.linkedin.com/in/fris/'),
--]
--
--
--# --- primitives ------------------------------------------------------------
--def round_rect(c, x, y, w, h, r, fill, stroke=BLUE_LINE, stroke_width=1,
-- shadow=True, shadow_dx=4, shadow_dy=-4):
-- if shadow:
-- c.setFillColor(SHADOW)
-- c.setStrokeColor(SHADOW)
-- c.roundRect(x + shadow_dx, y + shadow_dy, w, h, r, fill=1, stroke=0)
-- c.setFillColor(fill)
-- c.setStrokeColor(stroke)
-- c.setLineWidth(stroke_width)
-- c.roundRect(x, y, w, h, r, fill=1, stroke=1)
--
--
--def para(c, html, style, x, top, width, height=400):
-- p = Paragraph(html, style)
-- _, h = p.wrap(width, height)
-- p.drawOn(c, x, top - h)
-- return h
--
--
--def draw_icon_circle(c, x, y, size, glyph, fg=BLUE, bg=CARD_BG,
-- font_size=15, stroke=BLUE_LINE):
-- c.setFillColor(bg)
-- c.setStrokeColor(stroke)
-- c.setLineWidth(1.0)
-- c.circle(x + size / 2, y + size / 2, size / 2, fill=1, stroke=1)
-- c.setFont('FA', font_size)
-- c.setFillColor(fg)
-- c.drawCentredString(x + size / 2, y + size / 2 - font_size * 0.34, glyph)
--
--
--def section_header(c, x, y, w, text, glyph, h=26):
-- round_rect(c, x, y, w, h, h / 2, BLUE, stroke=BLUE, stroke_width=0.8,
-- shadow=True)
-- draw_icon_circle(c, x + 8, y + (h - 20) / 2, 20, glyph, fg=white,
-- bg=BLUE_SOFT, font_size=11, stroke=BLUE_SOFT)
-- c.setFont('Helvetica-Bold', 13)
-- c.setFillColor(white)
-- c.drawString(x + 36, y + (h - 13) / 2 + 1, text)
--
--
--def chip_width(text):
-- return pdfmetrics.stringWidth(text, 'Helvetica-Bold', 8.9) + 28
--
--
--def draw_chip(c, x, y, text, glyph, h=20):
-- w = chip_width(text)
-- c.setFillColor(BLUE_SOFT)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.8)
-- c.roundRect(x, y, w, h, h / 2, fill=1, stroke=1)
-- c.setFont('FA', 10)
-- c.setFillColor(white)
-- c.drawString(x + 9, y + 5, glyph)
-- c.setFont('Helvetica-Bold', 8.9)
-- c.drawString(x + 22, y + 6, text)
-- return w
--
--
--def draw_header(c, x, y, w, h):
-- round_rect(c, x, y, w, h, 26, BLUE, stroke=BLUE, stroke_width=0.8,
-- shadow=True)
-- c.setFillColor(white)
-- c.setFont('Helvetica-Bold', 30)
-- c.drawString(x + 22, y + h - 42, 'Alex Frison')
--
-- role_style = ParagraphStyle('role', fontName='Helvetica-Bold',
-- fontSize=10.4, leading=12.2, textColor=white)
-- role = ('Data Analyst &nbsp;&middot;&nbsp; Software Engineer &nbsp;&middot;&nbsp; '
-- 'Systems Architect')
-- para(c, role, role_style, x + 24, y + h - 50, 340, 14)
--
-- subtitle_style = ParagraphStyle('subtitle', fontName='Helvetica',
-- fontSize=9.0, leading=11.0,
-- textColor=white)
-- subtitle = ('Versatile builder across analytics, robotics support, '
-- 'retail sales leadership, and full-stack platforms. '
-- 'Pacific Northwest based &mdash; open to hybrid, remote, on-site.')
-- para(c, subtitle, subtitle_style, x + 24, y + h - 64, 340, 30)
--
-- # role chips, two rows
-- row1 = [('\uf201', 'Analytics / BI'),
-- ('\uf121', 'Full Stack Eng'),
-- ('\uf085', 'Robotics / RME')]
-- row2 = [('\uf07a', 'Sales Leadership'),
-- ('\uf0b1', 'Product Builder'),
-- ('\uf135', 'Systems Architect')]
-- cx = x + 24
-- for glyph, text in row1:
-- cx += draw_chip(c, cx, y + 36, text, glyph) + 8
-- cx = x + 24
-- for glyph, text in row2:
-- cx += draw_chip(c, cx, y + 12, text, glyph) + 8
--
-- # contact panel
-- cp_w, cp_h = 212, 92
-- cp_x, cp_y = x + w - cp_w - 18, y + h - cp_h - 16
-- round_rect(c, cp_x, cp_y, cp_w, cp_h, 16, BLUE_SOFT,
-- stroke=BLUE_LINE, stroke_width=0.9, shadow=False)
--
-- # three rows: phone / email / web
-- items = [
-- ('\uf095', '+1 (425) 367-3997', 'tel:+14253673997'),
-- ('\uf0e0', '[email protected]', 'mailto:[email protected]'),
-- ('\uf0ac', 'alexfrison.net', 'https://alexfrison.net'),
-- ('\uf041', 'Seattle / Arlington, WA', None),
-- ]
-- row_h = (cp_h - 16) / len(items)
-- for i, (glyph, text, link) in enumerate(items):
-- cy = cp_y + cp_h - 8 - (i + 1) * row_h
-- draw_icon_circle(c, cp_x + 10, cy + (row_h - 18) / 2, 18, glyph,
-- fg=white, bg=BLUE_SOFT, font_size=10,
-- stroke=HexColor('#a8c4ee'))
-- c.setFillColor(white)
-- c.setFont('Helvetica-Bold', 9.3)
-- c.drawString(cp_x + 36, cy + (row_h - 9) / 2 + 1, text)
-- if link:
-- c.linkURL(link, (cp_x, cy, cp_x + cp_w, cy + row_h),
-- relative=0, thickness=0)
--
--
--def top_feature_card(c, x, y, w, h, glyph, title_html, bullets):
-- round_rect(c, x, y, w, h, 16, CARD_BG, shadow=True)
-- draw_icon_circle(c, x + 12, y + h - 38, 24, glyph, fg=BLUE, bg=CARD_BG,
-- font_size=12)
-- para(c, title_html, ParagraphStyle('ft', parent=CARD_TITLE, fontSize=10.6,
-- leading=11.8),
-- x + 46, y + h - 11, w - 56, 26)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.7)
-- c.line(x + 14, y + h - 46, x + w - 14, y + h - 46)
-- html = '<br/>'.join(f'&bull;&nbsp; {b}' for b in bullets)
-- para(c, html, ParagraphStyle('fb', parent=CARD_DESC, fontSize=8.0,
-- leading=9.2),
-- x + 14, y + h - 52, w - 28, 60)
--
--
--def metric_strip(c, x, y, w, h, metrics):
-- round_rect(c, x, y, w, h, 14, CARD_BG, shadow=True)
-- n = len(metrics)
-- col_w = w / n
-- ICON_BG = HexColor('#ddeaf8')
-- r_icon = 14 # radius → 28 pt diameter
-- for i, (num, lbl, glyph) in enumerate(metrics):
-- col_x = x + i * col_w
-- if i > 0:
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.7)
-- c.line(col_x, y + 8, col_x, y + h - 8)
-- # icon circle — left side, vertically centred
-- ic_cx = col_x + 8 + r_icon # circle centre x
-- ic_cy = y + h / 2 # circle centre y
-- c.setFillColor(ICON_BG)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.9)
-- c.circle(ic_cx, ic_cy, r_icon, fill=1, stroke=1)
-- c.setFont('FA', 14)
-- c.setFillColor(BLUE)
-- c.drawCentredString(ic_cx, ic_cy - 5, glyph)
-- # number — right of icon, upper area, centred in text zone
-- tx = col_x + 8 + r_icon * 2 + 6 # left edge of text
-- label_w = col_w - (tx - col_x) - 4 # text zone width
-- c.setFont('Helvetica-Bold', 18)
-- c.setFillColor(BLUE_DARK)
-- c.drawCentredString(tx + label_w / 2, y + h - 22, num)
-- # label — below number, centred in same text zone
-- para(c, lbl, METRIC_LBL, tx, y + h - 24, label_w, 28)
--
--
--def draw_experience(c, x, y_top, title, date, company, bullets, stack,
-- rule_gap=8, width=None):
-- if width is None:
-- width = PAGE_W - x - 34
-- title_w = width - 110
-- h = para(c, title, EXP_TITLE, x, y_top, title_w, 22)
-- c.setFont('Helvetica-Bold', 10.8)
-- c.setFillColor(BLUE)
-- c.drawRightString(x + width, y_top - 1, date)
-- y = y_top - max(h, 14) - 4
-- ch = para(c, company, COMPANY, x, y, width, 14)
-- y -= ch + 2
-- bullet_html = '<br/>'.join(f'&bull;&nbsp; {b}' for b in bullets)
-- bh = para(c, bullet_html, BULLET, x + 8, y, width - 8, 200)
-- y -= bh + 3
-- sh = para(c, '<font name="FA">\uf013</font>&nbsp; ' + stack, STACK,
-- x + 8, y, width - 8, 30)
-- bottom = y - sh - rule_gap
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.6)
-- c.line(x, bottom, x + width, bottom)
-- return bottom - 12
--
--
--def product_card(c, x, y, w, h, glyph, title, url, desc, link):
-- round_rect(c, x, y, w, h, 14, CARD_BG, shadow=True)
-- draw_icon_circle(c, x + 10, y + h - 28, 22, glyph, fg=BLUE, bg=CARD_BG,
-- font_size=11)
-- tx = x + 38
-- para(c, title, ParagraphStyle('pt', parent=PRODUCT_TITLE, fontSize=10.0,
-- leading=10.8),
-- tx, y + h - 8, w - (tx - x) - 10, 14)
-- para(c, f'<u>{url}</u>',
-- ParagraphStyle('pu', parent=URL_STYLE, fontSize=7.6, leading=8.4),
-- tx, y + h - 22, w - (tx - x) - 10, 12)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.5)
-- c.line(x + 12, y + h - 34, x + w - 12, y + h - 34)
-- para(c, desc,
-- ParagraphStyle('pd', parent=CARD_DESC, fontSize=7.7, leading=8.8),
-- x + 12, y + h - 38, w - 24, h - 42)
-- c.linkURL(link, (x, y, x + w, y + h), relative=0, thickness=0)
--
--
--def core_card(c, x, y, w, h, glyph, title, desc):
-- round_rect(c, x, y, w, h, 12, CARD_BG, shadow=True)
-- draw_icon_circle(c, x + 10, y + h - 28, 22, glyph, fg=BLUE, bg=CARD_BG,
-- font_size=11)
-- para(c, title,
-- ParagraphStyle('ct2', parent=CARD_TITLE, fontSize=9.4, leading=10.6),
-- x + 40, y + h - 8, w - 50, 18)
-- c.setStrokeColor(BLUE_LINE)
-- c.setLineWidth(0.5)
-- c.line(x + 12, y + h - 32, x + w - 12, y + h - 32)
-- para(c, desc,
-- ParagraphStyle('cd2', parent=CARD_DESC, fontSize=7.7, leading=9.0),
-- x + 12, y + h - 36, w - 24, h - 40)
--
--
--def links_strip(c, x, y, w, h, links):
-- round_rect(c, x, y, w, h, h / 2, CARD_BG, shadow=False)
-- cx = x + 14
-- for glyph, text, url in links:
-- draw_icon_circle(c, cx, y + (h - 18) / 2, 18, glyph, fg=BLUE,
-- bg=CARD_BG, font_size=10)
-- c.setFont('Helvetica-Bold', 9.2)
-- c.setFillColor(BLUE_DARK)
-- c.drawString(cx + 24, y + h / 2 - 3, text)
-- tw = pdfmetrics.stringWidth(text, 'Helvetica-Bold', 9.2)
-- link_w = 24 + tw + 20
-- c.linkURL(url, (cx - 4, y, cx + link_w, y + h),
-- relative=0, thickness=0)
-- cx += link_w + 6
--
--
--# --- pages -----------------------------------------------------------------
--def build(path):
-- c = canvas.Canvas(path, pagesize=letter)
-- c.setTitle('Alex Frison Resume')
-- c.setAuthor('Alex Frison')
-- c.setSubject('Resume — Data Analyst · Software Engineer · Systems Architect')
--
-- margin = 18
--
-- # ---- page 1 ----
-- c.setFillColor(BG)
-- c.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
--
-- header_h = 152
-- header_y = PAGE_H - margin - header_h
-- draw_header(c, margin, header_y, PAGE_W - 2 * margin, header_h)
--
-- # top feature cards
-- gap = 10
-- card_w = (PAGE_W - 2 * margin - gap * 3) / 4
-- card_h = 92
-- top_y = header_y - 8 - card_h
-- for i, card in enumerate(TOP_FEATURE_CARDS):
-- cx = margin + i * (card_w + gap)
-- top_feature_card(c, cx, top_y, card_w, card_h, *card)
--
-- # metrics strip
-- ms_h = 52
-- ms_y = top_y - 4 - ms_h
-- metric_strip(c, margin, ms_y, PAGE_W - 2 * margin, ms_h, METRICS)
--
-- # profile (compact)
-- prof_header_y = ms_y - 4 - 24
-- section_header(c, margin, prof_header_y, PAGE_W - 2 * margin,
-- 'PROFILE', '\uf2c0', h=24)
-- # profile box — auto-height based on measured text
-- _prof_style = ParagraphStyle('_pp', parent=BODY, fontSize=9.0, leading=10.8)
-- _prof_para = Paragraph(PROFILE, _prof_style)
-- _, _prof_text_h = _prof_para.wrap(PAGE_W - 2 * margin - 28, 400)
-- prof_box_h = int(_prof_text_h) + 18 # 9pt top + 9pt bottom pad
-- prof_box_y = prof_header_y - 4 - prof_box_h
-- round_rect(c, margin, prof_box_y, PAGE_W - 2 * margin, prof_box_h, 12,
-- CARD_BG, shadow=False)
-- para(c, PROFILE, _prof_style,
-- margin + 14, prof_box_y + prof_box_h - 9,
-- PAGE_W - 2 * margin - 28, prof_box_h - 18)
--
-- # experience header
-- exp_y = prof_box_y - 8 - 24
-- section_header(c, margin, exp_y, PAGE_W - 2 * margin, 'EXPERIENCE',
-- '\uf0b1', h=24)
-- cur_y = exp_y - 10
-- for e in EXPERIENCE[:3]:
-- cur_y = draw_experience(c, margin + 12, cur_y, e['title'], e['date'],
-- e['company'], e['bullets'], e['stack'],
-- width=PAGE_W - 2 * margin - 24)
--
-- # page footer removed to avoid bottom-overlap; pagination is visually obvious.
--
-- c.showPage()
--
-- # ---- page 2 ----
-- c.setFillColor(BG)
-- c.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
--
-- y = PAGE_H - margin - 4
-- section_header(c, margin, y - 24, PAGE_W - 2 * margin,
-- 'EXPERIENCE (CONT.)', '\uf0b1', h=24)
-- y -= 36
-- for e in EXPERIENCE[3:5]:
-- y = draw_experience(c, margin + 12, y, e['title'], e['date'],
-- e['company'], e['bullets'], e['stack'],
-- width=PAGE_W - 2 * margin - 24)
--
-- # selected products
-- section_header(c, margin, y - 10, PAGE_W - 2 * margin,
-- 'SELECTED PROJECTS · LIVE EXAMPLES', '\uf0c1', h=24)
-- y -= 6
-- pw = (PAGE_W - 2 * margin - 10) / 2
-- ph = 70
-- for idx, prj in enumerate(PROJECTS):
-- col = idx % 2
-- row = idx // 2
-- px = margin + col * (pw + 10)
-- py = y - 6 - row * (ph + 6) - ph
-- product_card(c, px, py, pw, ph, *prj)
-- rows = (len(PROJECTS) + 1) // 2
-- y = y - 6 - rows * (ph + 6) - 18
--
-- # core stack
-- section_header(c, margin, y - 4, PAGE_W - 2 * margin,
-- 'CORE TECHNICAL STACK', '\uf1c0', h=24)
-- y -= 6
-- cw = (PAGE_W - 2 * margin - 10) / 2
-- ch = 70
-- for i in range(len(CORE_STACK)):
-- col = i % 2
-- row = i // 2
-- cx = margin + col * (cw + 10)
-- cy = y - 6 - row * (ch + 6) - ch
-- g, t, d = CORE_STACK[i]
-- core_card(c, cx, cy, cw, ch, g, t, d)
-- n_rows = (len(CORE_STACK) + 1) // 2
-- y = y - 6 - n_rows * (ch + 6) - 18
--
-- # education + links
-- section_header(c, margin, y - 4, PAGE_W - 2 * margin,
-- 'EDUCATION · LEARNING · ELSEWHERE', '\uf19d', h=24)
-- y -= 14
-- edu_text = ('<b>Snohomish High School, WA</b> &mdash; AP Statistics, '
-- 'Calculus, English, Creative Writing, Spanish. Self-taught '
-- 'systems builder since age 12 &mdash; analytics, '
-- 'infrastructure, graphics, photography, hardware, and '
-- 'practical problem solving.')
-- _, _edu_h = Paragraph(edu_text, LEARN).wrap(PAGE_W - 2 * margin - 28, 60)
-- edu_h = int(_edu_h) + 14
-- round_rect(c, margin, y - edu_h, PAGE_W - 2 * margin, edu_h, 12, CARD_BG,
-- shadow=False)
-- para(c, edu_text, LEARN, margin + 14, y - 7, PAGE_W - 2 * margin - 28, _edu_h)
-- y -= edu_h + 8
--
-- links_strip(c, margin, y - 34, PAGE_W - 2 * margin, 34, LINKS)
--
-- c.setFont('Helvetica', 7.5)
-- c.setFillColor(SUBTLE)
-- c.drawCentredString(PAGE_W / 2, 6,
-- 'Alex Frison · Resume 2026 · alexfrison.net')
--
-- c.showPage()
-- c.save()
--
--
--if __name__ == '__main__':
-- build('Alex_Frison_Resume.pdf')
-- print('Wrote Alex_Frison_Resume.pdf')
ddiff --git a/public/Alex_Frison_Resume.pdf b/public/Alex_Frison_Resume.pdf
ddeleted file mode 100755
iindex 9af06ff..0000000
BBinary files a/public/Alex_Frison_Resume.pdf and /dev/null differ
ddiff --git a/public/Alex_Frison_Resume_2026.pdf b/public/Alex_Frison_Resume_2026.pdf
ddeleted file mode 100755
iindex 9af06ff..0000000
BBinary files a/public/Alex_Frison_Resume_2026.pdf and /dev/null differ
ddiff --git a/public/Alex_Frison_Resume_May2026.pdf b/public/Alex_Frison_Resume_May2026.pdf
ddeleted file mode 100644
iindex 9af06ff..0000000
BBinary files a/public/Alex_Frison_Resume_May2026.pdf and /dev/null differ
ddiff --git a/public/Alex_Frison_Resume_May_2026.pdf b/public/Alex_Frison_Resume_May_2026.pdf
ddeleted file mode 100644
iindex 9ed436a..0000000
BBinary files a/public/Alex_Frison_Resume_May_2026.pdf and /dev/null differ
ddiff --git a/public/Alex_Frison_Resume_oldddd.dox b/public/Alex_Frison_Resume_oldddd.dox
ddeleted file mode 100755
iindex 54bd65d..0000000
BBinary files a/public/Alex_Frison_Resume_oldddd.dox and /dev/null differ
ddiff --git a/public/Alex_Frison_Resume_olddddd.pdf b/public/Alex_Frison_Resume_olddddd.pdf
ddeleted file mode 100644
iindex e04d18f..0000000
BBinary files a/public/Alex_Frison_Resume_olddddd.pdf and /dev/null differ
ddiff --git a/public/assets/Alex_Frison_Resume.docx b/public/assets/Alex_Frison_Resume.docx
ddeleted file mode 100755
iindex 54bd65d..0000000
BBinary files a/public/assets/Alex_Frison_Resume.docx and /dev/null differ
ddiff --git a/public/assets/Alex_Frison_Resume.pdf b/public/assets/Alex_Frison_Resume.pdf
ddeleted file mode 100644
iindex e04d18f..0000000
BBinary files a/public/assets/Alex_Frison_Resume.pdf and /dev/null differ
ddiff --git a/src/components/ContactBlock.astro b/src/components/ContactBlock.astro
iindex 5d55fec..703945d 100644
---- a/src/components/ContactBlock.astro
++++ b/src/components/ContactBlock.astro
@@@ -24,7 +24,7 @@
</div>
<div class="contact__social">
-- <a class="contact__social-btn contact__social-btn--github" href="https://github.com/HoboStreamer" rel="me noopener" target="_blank">
++ <a class="contact__social-btn contact__social-btn--github" href="https://github.com/OpenVibers" rel="me noopener" target="_blank">
<i class="fa-brands fa-github"></i>
<span>GitHub</span>
</a>
ddiff --git a/src/components/Footer.astro b/src/components/Footer.astro
iindex c5fa195..2460a44 100644
---- a/src/components/Footer.astro
++++ b/src/components/Footer.astro
@@@ -10,7 +10,7 @@ const year = new Date().getFullYear();
<div class="foot__links">
<a href="mailto:[email protected]" title="Email"><i class="fa-solid fa-envelope"></i> [email protected]</a>
<a href="tel:+14253673997" title="Phone"><i class="fa-solid fa-phone"></i> (425) 367-3997</a>
-- <a href="https://github.com/HoboStreamer" rel="me noopener" target="_blank" title="GitHub"><i class="fa-brands fa-github"></i> GitHub</a>
++ <a href="https://github.com/OpenVibers" rel="me noopener" target="_blank" title="GitHub"><i class="fa-brands fa-github"></i> GitHub</a>
<a href="https://www.linkedin.com/in/fris/" rel="me noopener" target="_blank" title="LinkedIn"><i class="fa-brands fa-linkedin"></i> LinkedIn</a>
</div>
</div>
ddiff --git a/src/components/Hero.astro b/src/components/Hero.astro
iindex 888a23e..ed57e17 100644
---- a/src/components/Hero.astro
++++ b/src/components/Hero.astro
@@@ -25,10 +25,9 @@
<span class="hero__rotator" aria-live="polite"><i class="fa-solid fa-chart-line"></i> Data Analyst</span>
</p>
<p class="hero__lead">
-- Data analyst, systems architect, and sales leader with 10+ years of shipping across industries — from
-- SOX-compliance tooling and autonomous AI agents at Amazon ICQA to a
-- solo-built 15-microservice open-source creator platform, with roots in
-- retail sales management at Verizon Wireless (Sales Rep → Assistant Manager).
++ Data analyst and full-stack builder with 10+ years of shipping —
++ SOX-compliance tooling and autonomous AI agents at Amazon, a solo-built
++ 15-microservice creator platform, and sales leadership at Verizon.
Open to new roles.
</p>
<div class="hero__cta">
@@@ -39,11 +38,9 @@
<div class="hero__chips">
<span class="chip"><i class="fa-solid fa-chart-line"></i> Analytics / BI</span>
<span class="chip"><i class="fa-solid fa-code"></i> Full-Stack Eng</span>
-- <span class="chip"><i class="fa-solid fa-robot"></i> Autonomous Agents</span>
-- <span class="chip"><i class="fa-solid fa-gear"></i> Robotics / AFM</span>
-- <span class="chip"><i class="fa-solid fa-hammer"></i> Product Builder</span>
-- <span class="chip"><i class="fa-solid fa-comment-dollar"></i> Sales</span>
-- <span class="chip"><i class="fa-solid fa-users"></i> Management / Leadership</span>
++ <span class="chip"><i class="fa-solid fa-robot"></i> AI Agents</span>
++ <span class="chip"><i class="fa-solid fa-gear"></i> Robotics</span>
++ <span class="chip"><i class="fa-solid fa-users"></i> Sales & Leadership</span>
</div>
</div>
@@@ -113,11 +110,15 @@
.hero__portrait { width: min(220px, 55vw); margin-bottom: 8px; }
}
++ /* Solid color on purpose — background-clip:text + color:transparent breaks
++ when Splitting.js wraps chars in inline-block spans (text renders invisible
++ on some GPUs/viewports, e.g. Steam Deck at 1280×800). Same bug class as the
++ earlier ImpactMetrics "invisible numbers" fix. */
.hero__title {
-- font-size: clamp(3rem, 9vw, 6.5rem);
-- background: linear-gradient(180deg, #fff 30%, #8aa0b6 100%);
-- -webkit-background-clip: text; background-clip: text;
-- color: transparent;
++ font-size: clamp(2.6rem, 7vw, 5.5rem);
++ color: var(--fg);
++ text-shadow: 0 2px 24px rgba(0, 0, 0, 0.35);
++ overflow-wrap: break-word;
margin-bottom: 0.4em;
}
.hero__role {
@@@ -217,13 +218,10 @@
const roles = [
`<i class="fa-solid fa-chart-line"></i> Data Analyst`,
`<i class="fa-solid fa-code"></i> Software Engineer`,
-- `<i class="fa-solid fa-robot"></i> Autonomous Agent Builder`,
++ `<i class="fa-solid fa-robot"></i> AI Agent Builder`,
`<i class="fa-solid fa-server"></i> Systems Admin`,
-- `<i class="fa-solid fa-cube"></i> Product Engineer`,
-- `<i class="fa-brands fa-twitch"></i> Streaming Platform Builder`,
-- `<i class="fa-solid fa-tree"></i> Pacific Northwest Native`,
-- `<i class="fa-solid fa-comment-dollar"></i> Sales`,
-- `<i class="fa-solid fa-users"></i> Management / Leadership`
++ `<i class="fa-solid fa-users"></i> Sales Leader`,
++ `<i class="fa-solid fa-tree"></i> Pacific Northwest Native`
];
const rot = document.querySelector(".hero__rotator");
if (rot) {
ddiff --git a/src/components/ImpactMetrics.astro b/src/components/ImpactMetrics.astro
iindex dabb9e1..533b89c 100644
---- a/src/components/ImpactMetrics.astro
++++ b/src/components/ImpactMetrics.astro
@@@ -1,10 +1,10 @@
---
const metrics = [
-- { num: "1,000+", label: "SOX-compliance IRDRs hand-researched at Amazon ICQA", icon: "fa-solid fa-magnifying-glass-chart" },
-- { num: "15", label: "OpenVibe microservices designed, built, and shipped", icon: "fa-solid fa-cubes-stacked" },
-- { num: "200K+", label: "Live game servers tracked across GameServerStats", icon: "fa-solid fa-tower-broadcast" },
-- { num: "20+", label: "Amazon TamperMonkey/Python automation scripts", icon: "fa-solid fa-code" },
-- { num: "10+", label: "Years running my own LLC end-to-end", icon: "fa-solid fa-building-flag" },
++ { num: "1,000+", label: "SOX-compliance IRDRs researched at Amazon", icon: "fa-solid fa-magnifying-glass-chart" },
++ { num: "15", label: "OpenVibe microservices built solo", icon: "fa-solid fa-cubes-stacked" },
++ { num: "200K+", label: "Live game servers tracked", icon: "fa-solid fa-tower-broadcast" },
++ { num: "20+", label: "Automation scripts for Amazon workflows", icon: "fa-solid fa-code" },
++ { num: "10+", label: "Years running my own LLC", icon: "fa-solid fa-building-flag" },
];
---
<section class="impact" aria-labelledby="impact-title" data-reveal>
ddiff --git a/src/content/experience/01-amazon-data-analyst.json b/src/content/experience/01-amazon-data-analyst.json
iindex 8ac756a..70d9246 100644
---- a/src/content/experience/01-amazon-data-analyst.json
++++ b/src/content/experience/01-amazon-data-analyst.json
@@@ -5,10 +5,10 @@
"end": "2025",
"location": "PAE2 · Arlington, WA",
"bullets": [
-- "Own SOX-compliance IRDR (Inventory Discrepancy Research Report) workflows — hand-researched 1,000+ inventory mismatches and built the data systems that support Sarbanes-Oxley audit requirements.",
-- "Built ICQA SQL and Excel reporting for inventory accuracy, discrepancy review, and defect visibility; surfaced findings in dashboards and recurring leadership reports.",
-- "Authored an Anthropic-powered SQL copilot that contextually references every available data table for analysts.",
-- "Built and shipped an autonomous root-cause-analysis bot that performs full end-to-end inventory research."
++ "Owned SOX-compliance IRDR workflows — researched 1,000+ inventory discrepancies and built the data systems behind Sarbanes-Oxley audit requirements.",
++ "Built SQL and Excel reporting for inventory accuracy and defect visibility; delivered dashboards and recurring leadership reports.",
++ "Created an Anthropic-powered SQL copilot that references every available data table for analysts.",
++ "Shipped an autonomous root-cause bot that performs end-to-end inventory research."
],
"stack": ["SQL", "Excel", "Python", "Anthropic API", "TamperMonkey", "Power Query", "SOX Compliance"],
"featured": true,
ddiff --git a/src/content/experience/02-amazon-rme.json b/src/content/experience/02-amazon-rme.json
iindex 235b38d..2ff7f55 100644
---- a/src/content/experience/02-amazon-rme.json
++++ b/src/content/experience/02-amazon-rme.json
@@@ -5,9 +5,9 @@
"end": "2024",
"location": "Amazon Robotics launch site",
"bullets": [
-- "Monitored Amazon Robotics (ARS) automation floor during launch-stage operations — enforcing amnesty zones, clearing obstructions, and maintaining safe pod/drive flow.",
-- "Investigated pod, drive unit, and workstation issues on the floor; escalated recurring defects and documented patterns to support process improvement.",
-- "Coordinated amnesty events, ensured safe robot re-entry after floor interventions, and communicated status to ops leadership in real time."
++ "Monitored the Amazon Robotics floor during launch operations — enforced amnesty zones, cleared obstructions, and kept pod/drive flow safe.",
++ "Investigated pod, drive unit, and workstation issues; documented recurring defects to drive process improvement.",
++ "Coordinated amnesty events and communicated floor status to ops leadership in real time."
],
"stack": ["Amazon Robotics", "AFM protocols", "Floor safety", "Amnesty ops"],
"featured": true,
ddiff --git a/src/content/experience/03-verizon.json b/src/content/experience/03-verizon.json
iindex e55c289..e0858d1 100644
---- a/src/content/experience/03-verizon.json
++++ b/src/content/experience/03-verizon.json
@@@ -4,10 +4,10 @@
"start": "2020",
"end": "2023",
"bullets": [
-- "Consistently ranked among top performers in the district — hit and exceeded monthly quotas across smartphones, plans, accessories, device protection, financing, and Verizon Visa card offers.",
-- "Promoted to Assistant Manager; mentored and coached a team of 4–8 reps through weekly 1-on-1s, floor coaching, and monthly skill workshops covering objection handling, upselling, and customer retention.",
-- "Led store opens/closes, managed inventory, resolved escalated customer issues, and ran daily huddles to align the team on promos and targets.",
-- "Built genuine rapport with customers — turned walk-ins into multi-line accounts and walk-outs into long-term brand advocates through consultative, needs-first selling."
++ "Ranked among the district's top performers — consistently exceeded monthly quotas across phones, plans, accessories, protection, and financing.",
++ "Promoted to Assistant Manager; coached a team of 4–8 reps through 1-on-1s, floor coaching, and skill workshops.",
++ "Ran store opens/closes, inventory, escalations, and daily huddles.",
++ "Turned walk-ins into multi-line accounts through consultative, needs-first selling."
],
"stack": ["Consultative Sales", "Team Leadership", "Coaching", "Retail Ops", "Quota Attainment", "Customer Retention"],
"order": 3
ddiff --git a/src/content/experience/04-openvibe.json b/src/content/experience/04-openvibe.json
iindex 82d4622..9041e39 100644
---- a/src/content/experience/04-openvibe.json
++++ b/src/content/experience/04-openvibe.json
@@@ -4,12 +4,12 @@
"start": "2022",
"end": "2025",
"bullets": [
-- "Sole architect and engineer on OpenVibe — a 15-microservice open-source platform kernel: distributed event bus, OIDC/OAuth2 identity provider, capability contract registry, media object store, live-streaming ingest and restream, real-time chat, billing ledger, AI/search backbone, and realtime infrastructure. Self-hosted alternative to Twitch + YouTube + Patreon + Discord. 16 phases documented; 11+ fully implemented with cutover runbook and migration tooling complete.",
-- "Built and operated HoboStreamer from scratch — a full live-streaming platform with WebRTC SFU, HLS, multi-destination RTMP restream (OBS → Twitch/YouTube/Kick simultaneously), auto-recorded VODs, clip detection, real-time chat with custom emotes, voice channels, stream analytics, comprehensive moderation tools, and an integrated browser game for viewers. Fully open source.",
-- "Built HoboQuest — a browser MMORPG with a custom isomorphic game engine: wrote all game logic once in shared.js, compiled to both server and client via a custom MD5-diffing compiler. Source Engine-style lag compensation (client-side prediction + server reconciliation) drives the real-time multiplayer. Server-master.js orchestrates 4 independent game instances with distinct rulesets (PvP world, social hub, farm island, ship minigame). Custom Canvas 2D rendering engine with SAT collision detection. Open-world survival: building, combat, NPC AI, 7+ skills, inventory/crafting/bank, bus fast travel. In-game scratch card and slot casino; mobile touch and gamepad input. Integrated live into HoboStreamer — viewers watch and play in the same session.",
-- "Maintained hobo.tools — central SSO and OAuth2 identity network across 14 interconnected services, including shared auth, service registry, image processing, maps, audio, and cross-domain session management."
++ "Sole architect of OpenVibe — a 15-microservice open-source platform kernel: event bus, OIDC/OAuth2 identity, media storage, live-streaming ingest/restream, real-time chat, billing ledger, and AI/search backbone. A self-hosted alternative to Twitch + YouTube + Patreon + Discord.",
++ "Built and operated HoboStreamer — a full live-streaming platform with WebRTC, HLS, multi-destination RTMP restream, auto-recorded VODs, clips, real-time chat, voice channels, and moderation tools. Fully open source.",
++ "Built HoboQuest — a browser MMORPG on a custom isomorphic engine: shared game logic compiled to both server and client, Source Engine-style lag compensation, and four live game worlds. Playable in a tab, streamed live inside HoboStreamer.",
++ "Ran hobo.tools — shared SSO/OAuth2 identity and service registry connecting 14 services."
],
-- "stack": ["TypeScript", "Node.js", "Express", "SQLite", "PostgreSQL", "WebRTC", "WebSockets", "Redis", "OAuth2", "OIDC", "FFmpeg", "Lua", "C#", "React", "Vue"],
++ "stack": ["TypeScript", "Node.js", "Express", "SQLite", "PostgreSQL", "WebRTC", "WebSockets", "Redis", "OAuth2", "OIDC", "FFmpeg", "React", "Vue"],
"featured": true,
"order": 4
}
ddiff --git a/src/content/projects/amazon-sql-copilot.mdx b/src/content/projects/amazon-sql-copilot.mdx
iindex aeea688..7094b06 100644
---- a/src/content/projects/amazon-sql-copilot.mdx
++++ b/src/content/projects/amazon-sql-copilot.mdx
@@@ -1,7 +1,7 @@
---
title: "Amazon SQL Copilot"
category: "amazon"
--summary: "Anthropic-powered chatbot that generates SQL queries, intelligently referencing context from every available database table for ICQA data analysts."
++summary: "Anthropic-powered copilot that writes runnable, schema-aware SQL for ICQA analysts — no memorizing 200+ table names."
tech: ["Python", "Anthropic API", "SQL", "Schema Indexing", "Streamlit"]
role: "Sole builder"
year: "2025"
ddiff --git a/src/content/projects/amazon-userscripts.mdx b/src/content/projects/amazon-userscripts.mdx
iindex 73a4aa8..88eaac2 100644
---- a/src/content/projects/amazon-userscripts.mdx
++++ b/src/content/projects/amazon-userscripts.mdx
@@@ -1,7 +1,7 @@
---
title: "Amazon Userscript Suite"
category: "amazon"
--summary: "Family of TamperMonkey userscripts that supercharge the daily ICQA + AFM workflow: IRDRTools, FCQuality Apex Ultra, FC/Prosearch, GCAHistory, FCLM Indirect, Oculus."
++summary: "Six TamperMonkey userscripts that remove daily friction from internal ICQA and AFM tooling — deep links, table enrichment, bulk actions."
tech: ["JavaScript", "TamperMonkey", "DOM APIs", "Internal Web Tools"]
role: "Sole builder"
year: "2024–2026"
ddiff --git a/src/content/projects/hobo-quest.mdx b/src/content/projects/hobo-quest.mdx
iindex e0df231..7b776d9 100644
---- a/src/content/projects/hobo-quest.mdx
++++ b/src/content/projects/hobo-quest.mdx
@@@ -1,7 +1,7 @@
---
title: "HoboQuest"
category: "hobo"
--summary: "Browser MMORPG with a custom isomorphic game engine, Source Engine-style lag compensation, and 4-instance server architecture — no downloads, open a tab and play."
++summary: "Browser MMORPG on a custom-built engine with Source-style lag compensation — no downloads, open a tab and play."
tech: ["JavaScript", "Node.js", "WebSockets", "Canvas API", "Express", "isomorphic compiler", "lag compensation", "Canvas 2D physics"]
role: "Founder / Engine & Gameplay Engineer"
year: "2023–Present"
@@@ -11,30 +11,16 @@ featured: true
order: 12
---
--A persistent browser MMORPG built on a custom-engineered isomorphic game engine — no Unity, no Godot, no game framework. Everything from the physics and collision system to the multiplayer protocol was written from scratch.
++A persistent browser MMORPG built on a custom engine — no Unity, no Godot. Physics, collision, and the multiplayer protocol were all written from scratch.
--**Custom isomorphic compile pipeline.** All game logic lives in a single `shared.js` file containing conditional compilation blocks (`/*IF_SERVER*/.../*IF_END*/`). A custom `compiler.js` transforms it into `server.js` and `public/client.js` — the same physics, entity, and world state logic runs on both sides, preventing desyncs by design. MD5 change detection skips unnecessary recompiles.
++**Isomorphic engine.** All game logic lives in one shared file; a custom compiler emits both the server and client builds, so the exact same physics and world-state code runs on both sides — desyncs are prevented by design.
--**Source Engine-style lag compensation.** Real-time multiplayer uses client-side prediction with server reconciliation — the same methodology used in Counter-Strike and Valve's Source Engine. Movement and combat feel instant for the player while the server remains authoritative over all state.
++**Source Engine-style netcode.** Client-side prediction with server reconciliation (the Counter-Strike approach): movement and combat feel instant while the server stays authoritative.
--**Multi-instance server architecture.** A `server-master.js` orchestrator spawns four independent game-server processes (ports 9000–9003), each running a different map with its own ruleset:
--- Default world — PvP enabled, player building enabled
--- Central city — social hub with vendors; PvP disabled
--- Farm island — resource farming; PvP enabled, building disabled
--- Ship minigame — dedicated activity instance
++**Four live worlds.** An orchestrator runs four game instances with distinct rulesets — a PvP building world, a safe social hub, a farming island, and a ship minigame.
--**Canvas 2D rendering engine.** Built a custom Canvas 2D rendering layer (`lib/c2d.js`) with a full 2D separating-axis theorem (SAT) collision detection system, polygon/circle collision, voronoi region edge detection, and frame-accurate hit resolution — no third-party physics library.
++**Custom rendering & physics.** Canvas 2D renderer with full SAT collision detection (polygon/circle, frame-accurate hits) — no third-party physics library.
--**Open-world survival systems.** Tile-based 512×512 open world with:
--- **Building**: place and upgrade walls, doors, beds, tool cupboards, campfires, barbed wire
--- **Combat**: melee knockback, ranged pistol, arrows with ballistic gravity arc, NPC aggro with patrol/attack telegraph/hit flash
--- **Skills**: Woodcutting, Mining, Herbology, Attack, and more — persistent per player
--- **Economy**: inventory, crafting recipes, equipment slots, bank system, bus system for zone travel
++**Full survival loop.** Building, combat (melee, ranged, NPC AI), persistent skills, crafting, banking, fast travel — plus an in-game scratch-card and slots casino with server-validated state.
--**In-game casino.** Scratch card and slot machine minigames with a credit balance system, SoundManager 2 audio integration, and server-validated game state.
--
--**Cross-platform input.** Mobile touch controls overlay (tap/joystick buttons) with runtime touch detection, plus gamepad detection with a controller configuration UI. Desktop WASD + mouse.
--
--**Integrated with HoboStreamer.** The game is embedded live in the streaming platform — viewers watch and play simultaneously, with chat bridged between the game and the stream.
--
--The collaborative pixel canvas is a separate 512×512 real-time art board for territory battles and group art, also accessible from [hobo.quest](https://hobo.quest).
++**Play anywhere.** Desktop WASD + mouse, mobile touch overlay, and gamepad support. Embedded live in HoboStreamer, so viewers watch and play in the same session.
ddiff --git a/src/content/projects/irdr-autonomous-bot.mdx b/src/content/projects/irdr-autonomous-bot.mdx
iindex 1abc9e2..456759c 100644
---- a/src/content/projects/irdr-autonomous-bot.mdx
++++ b/src/content/projects/irdr-autonomous-bot.mdx
@@@ -1,7 +1,7 @@
---
title: "Autonomous IRDR Root-Cause Bot"
category: "amazon"
--summary: "Fully autonomous inventory root-cause-analysis agent that plans, gathers evidence, and writes a complete IRDR research report end-to-end — supporting SOX-compliant inventory accuracy."
++summary: "Autonomous agent that plans, gathers evidence, and writes complete SOX-compliant inventory research reports — hours of analysis down to seconds."
tech: ["Python", "Anthropic API", "Tool-use Agents", "TamperMonkey", "SQL", "SOX Compliance"]
role: "Sole builder"
year: "2025"
ddiff --git a/src/content/projects/openvibe.mdx b/src/content/projects/openvibe.mdx
iindex e4b256a..eb58c10 100644
---- a/src/content/projects/openvibe.mdx
++++ b/src/content/projects/openvibe.mdx
@@@ -1,7 +1,7 @@
---
title: "OpenVibe"
category: "hobo"
--summary: "Open-source creator platform kernel: 15+ microservices, distributed event bus, OIDC/OAuth2 identity, live streaming, media storage, billing ledger, AI backbone, and real-time infrastructure — sole architect and engineer across 16 documented phases."
++summary: "Open-source creator platform kernel — 15+ microservices spanning identity, live streaming, media, billing, and AI, designed and built by one person."
tech: ["Node.js", "TypeScript", "Express", "SQLite", "PostgreSQL", "WebRTC", "WebSockets", "Redis", "OAuth2", "OIDC", "FFmpeg", "REST APIs", "Event-Driven Architecture", "Microservices"]
role: "Sole Architect & Principal Engineer"
year: "2024–2026"
@@@ -10,24 +10,16 @@ featured: true
order: 5
---
--A fully open-source, self-hosted platform kernel designed to replace the Hobo Network and serve as a production-grade alternative to the entire creator economy stack — Twitch, YouTube, Patreon, Reddit, and Discord in a single deployable monorepo. Built by one person across 16 documented phases, with cutover runbook and migration tooling complete.
++A fully open-source, self-hosted platform kernel — Twitch, YouTube, Patreon, and Discord in one deployable monorepo. Designed and built by one person across 16 documented phases.
--**Services shipped (13 microservices):**
++**What's in it:**
--- **openvibe-network** — Control plane: OIDC/OAuth2 identity provider, service registry, capability contract registry, user-module namespace API, policy engine, and host-aware routing across `auth.openvibe.network`, `api.openvibe.network`, `admin.openvibe.network`
--- **openre-stream** — RTMP ingest and multi-destination restream (OBS → Twitch / YouTube / Kick / any RTMP endpoint simultaneously)
--- **openvibe-live** — Live streaming frontend: SSR viewer surface, DVR, viewer analytics, stream presence
--- **openvibe-media** — Media object store: VODs, clips, thumbnails, stream snapshots, paste/file storage — with namespace registry, derivative pipeline, quota enforcement, and lifecycle audit log
--- **openvibe-chat** — Real-time chat: moderation, purges, bans, custom emotes, channel threading
--- **openvibe-community** — Forums, reactions, community posts, Discord-style channels, paste versioning
--- **openvibe-billing** — Credits ledger, tips, VIP status tiers, payout engine, SOX-style audit trail
--- **openvibe-ai** — AI/search backbone: autonomous content tagging, SEO generation, analyst copilot, embedding-based search
--- **openvibe-games** — Embedded browser game surface (HoboQuest integration, live viewer participation)
--- **openvibe-events** — Distributed event bus: full audit envelope (`event_id`, `trace_id`, `version`, `actor`), topic + subscription filtering, ack/retry work queue, dead-letter tables
--- **openvibe-realtime** — WebSocket hub, pub/sub presence layer, fan-out infrastructure
--- **openvibe-workers** — Task queue, scheduled jobs, background processors, queue-native pipeline
--- **openvibe-content** — Content workflows: publishing pipelines, moderation queue, product workflow seams
++- **Identity & control plane** — OIDC/OAuth2 provider, service registry, capability contracts, policy engine
++- **Live streaming** — RTMP ingest, multi-destination restream (OBS → Twitch / YouTube / Kick simultaneously), DVR, viewer analytics
++- **Media** — object store for VODs, clips, and thumbnails with derivative pipeline, quotas, and audit log
++- **Community** — real-time chat with moderation and custom emotes, forums, Discord-style channels
++- **Billing** — credits ledger, tips, VIP tiers, payout engine with SOX-style audit trail
++- **AI & search** — autonomous content tagging, SEO generation, embedding-based search
++- **Infrastructure** — distributed event bus with full audit envelope, WebSocket/pub-sub realtime layer, task queues, and shared typed-contract packages (SQLite + PostgreSQL dual-mode persistence)
--**Shared packages:** `openvibe-contracts` (typed event schemas), `openvibe-sdk` (cross-service clients), `openvibe-persistence` (SQLite + PostgreSQL dual-mode with schema parity enforcement), `openvibe-queue`, `openvibe-runtime`, `openvibe-storage`, `openvibe-redis`, `openvibe-observability`
--
--**Phase completion:** 11+ of 16 phases fully implemented. Remaining phases paused at production-readiness stage — cutover runbook written, migration tooling from HoboStreamer/hobo.tools complete, schema drift checks green. Servers brought offline in May 2026 due to funding constraints during job search. The architecture is sound and the code is all there.
++**Status:** 11+ of 16 phases fully implemented; cutover runbook and migration tooling complete. Servers offline since May 2026 during job search — the architecture is sound and the code is all there.
ddiff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
iindex 57491d6..c83a6b6 100644
---- a/src/layouts/Base.astro
++++ b/src/layouts/Base.astro
@@@ -64,7 +64,7 @@ const pageUrl = canonical ?? new URL(Astro.url.pathname, Astro.site).toString();
"addressCountry": "US"
},
"sameAs": [
-- "https://github.com/HoboStreamer",
++ "https://github.com/OpenVibers",
"https://www.linkedin.com/in/fris/"
]
}
ddiff --git a/src/pages/index.astro b/src/pages/index.astro
iindex 2d9a914..5147354 100644
---- a/src/pages/index.astro
++++ b/src/pages/index.astro
@@@ -21,30 +21,24 @@ import ContactBlock from "../components/ContactBlock.astro";
<h2 class="section-title">A versatile builder</h2>
<p class="profile-para">
<i class="fa-solid fa-microchip profile-para__icon"></i>
-- <span>Data analyst and systems architect with 10+ years of shipping — from
-- <strong>SOX-compliance tooling at Amazon ICQA</strong> to autonomous
-- AI research agents to a <strong>15-microservice open-source platform
-- kernel</strong>, all built and operated solo. Strong at translating
-- operational context into production-grade tooling, stakeholder-ready
-- analysis, and systems that actually scale.</span>
++ <span>Data analyst and systems architect with 10+ years of shipping —
++ from <strong>SOX-compliance tooling at Amazon ICQA</strong> to
++ autonomous AI agents to a <strong>15-microservice open-source
++ platform</strong>, built and operated solo. I turn operational
++ context into tools and analysis stakeholders can act on.</span>
</p>
<p class="profile-para">
<i class="fa-solid fa-store profile-para__icon"></i>
-- <span>Before the tech career, I cut my teeth in retail sales at
-- <strong>Verizon Wireless</strong> — first as a Sales Representative,
-- then promoted to <strong>Assistant Manager</strong>. Quota-driven
-- environment, high customer volume, team coaching, and the kind of
-- communication pressure that either breaks you or makes you sharp.
-- It made me sharp. That foundation — reading people, closing clearly,
-- handling objections — still shows up every time I present data or
-- walk a stakeholder through a decision.</span>
++ <span>Before tech, I sold at <strong>Verizon Wireless</strong> —
++ promoted from Sales Rep to <strong>Assistant Manager</strong> in a
++ quota-driven store. Reading people, handling objections, and closing
++ clearly still shape how I present data and decisions.</span>
</p>
<p class="profile-para">
<i class="fa-solid fa-screwdriver-wrench profile-para__icon"></i>
-- <span>Tech-geek since 12, building computers and chasing bleeding-edge
-- hardware. A decade running my own LLC, ten years tracking 200,000+
-- live game servers, and 1,000+ IRDRs personally researched at Amazon.
-- I ship fast and keep what works.</span>
++ <span>Building computers since age 12. A decade running my own LLC,
++ ten years tracking 200,000+ live game servers, 1,000+ IRDRs
++ researched at Amazon. I ship fast and keep what works.</span>
</p>
</div>
<aside class="card profile__facts" data-reveal>
@@@ -54,7 +48,7 @@ import ContactBlock from "../components/ContactBlock.astro";
<li><strong><i class="fa-solid fa-briefcase"></i> Open to</strong>Hybrid · Remote · On-site PNW</li>
<li><strong><i class="fa-solid fa-building-columns"></i> Founder</strong>GameServerStats, LLC</li>
<li><strong><i class="fa-solid fa-laptop-code"></i> Last role</strong>Amazon · Data Analyst (ICQA)</li>
-- <li class="profile__facts-seeking"><strong><i class="fa-solid fa-magnifying-glass"></i> Seeking</strong><span class="seek-groups"><span class="seek-group"><i class="fa-solid fa-chart-line"></i> Analyst · Data Engineer · BI</span><span class="seek-group"><i class="fa-solid fa-server"></i> IT · Systems · Robotics · Technician</span><span class="seek-group"><i class="fa-solid fa-people-group"></i> Sales · Leadership · Management</span><span class="seek-group"><i class="fa-solid fa-hammer"></i> Builder · Architect · Maker</span></span></li>
++ <li class="profile__facts-seeking"><strong><i class="fa-solid fa-magnifying-glass"></i> Seeking</strong><span class="seek-groups"><span class="seek-group"><i class="fa-solid fa-chart-line"></i> Analyst · Data Engineer · BI</span><span class="seek-group"><i class="fa-solid fa-server"></i> IT · Systems · Robotics</span><span class="seek-group"><i class="fa-solid fa-people-group"></i> Sales · Leadership · Management</span></span></li>
</ul>
</aside>
</div>