Compare commits

..

No commits in common. "master" and "feature/professional-homepage" have entirely different histories.

35 changed files with 1301 additions and 1944 deletions

View File

@ -43,7 +43,6 @@ def request_raw(
*, *,
payload: Any | None = None, payload: Any | None = None,
params: dict[str, Any] | None = None, params: dict[str, Any] | None = None,
timeout_sec: float | None = None,
) -> httpx.Response: ) -> httpx.Response:
"""Send one authenticated request to Ariadne and return the raw response. """Send one authenticated request to Ariadne and return the raw response.
@ -56,10 +55,9 @@ def request_raw(
url = _url(path) url = _url(path)
attempts = max(1, settings.ARIADNE_RETRY_COUNT) attempts = max(1, settings.ARIADNE_RETRY_COUNT)
request_timeout = timeout_sec if timeout_sec is not None else settings.ARIADNE_TIMEOUT_SEC
for attempt in range(1, attempts + 1): for attempt in range(1, attempts + 1):
try: try:
with httpx.Client(timeout=request_timeout) as client: with httpx.Client(timeout=settings.ARIADNE_TIMEOUT_SEC) as client:
resp = client.request( resp = client.request(
method, method,
url, url,
@ -80,7 +78,7 @@ def request_raw(
"method": method, "method": method,
"path": path, "path": path,
"attempt": attempt, "attempt": attempt,
"timeout_sec": request_timeout, "timeout_sec": settings.ARIADNE_TIMEOUT_SEC,
"error": str(exc), "error": str(exc),
}, },
) )
@ -95,7 +93,6 @@ def proxy(
*, *,
payload: Any | None = None, payload: Any | None = None,
params: dict[str, Any] | None = None, params: dict[str, Any] | None = None,
timeout_sec: float | None = None,
) -> tuple[Any, int]: ) -> tuple[Any, int]:
"""Proxy an Ariadne response through Flask as JSON plus status code. """Proxy an Ariadne response through Flask as JSON plus status code.
@ -104,7 +101,7 @@ def proxy(
""" """
try: try:
resp = request_raw(method, path, payload=payload, params=params, timeout_sec=timeout_sec) resp = request_raw(method, path, payload=payload, params=params)
except AriadneError as exc: except AriadneError as exc:
return jsonify({"error": str(exc)}), exc.status_code return jsonify({"error": str(exc)}), exc.status_code

View File

@ -5,7 +5,7 @@ from typing import Any
from flask import jsonify, request from flask import jsonify, request
from .. import ariadne_client, settings from .. import ariadne_client
from ..keycloak import require_auth, require_account_access from ..keycloak import require_auth, require_account_access
@ -142,12 +142,7 @@ def register_account_wolf(app) -> None:
ok, resp = _require_account() ok, resp = _require_account()
if not ok: if not ok:
return resp return resp
return ariadne_client.proxy( return ariadne_client.proxy("POST", "/api/admin/game-mode/start", payload=_json_payload())
"POST",
"/api/admin/game-mode/start",
payload=_json_payload(),
timeout_sec=settings.ARIADNE_GAME_MODE_TIMEOUT_SEC,
)
@app.route("/api/account/wolf/game-mode/stop", methods=["POST"]) @app.route("/api/account/wolf/game-mode/stop", methods=["POST"])
@require_auth @require_auth
@ -155,12 +150,7 @@ def register_account_wolf(app) -> None:
ok, resp = _require_account() ok, resp = _require_account()
if not ok: if not ok:
return resp return resp
return ariadne_client.proxy( return ariadne_client.proxy("POST", "/api/admin/game-mode/stop", payload=_json_payload())
"POST",
"/api/admin/game-mode/stop",
payload=_json_payload(),
timeout_sec=settings.ARIADNE_GAME_MODE_TIMEOUT_SEC,
)
@app.route("/api/account/wolf/admin/firewall/unlock", methods=["POST"]) @app.route("/api/account/wolf/admin/firewall/unlock", methods=["POST"])
@require_auth @require_auth

View File

@ -22,18 +22,18 @@ def register(app) -> None:
payload = request.get_json(silent=True) or {} payload = request.get_json(silent=True) or {}
user_message = (payload.get("message") or "").strip() user_message = (payload.get("message") or "").strip()
profile = (payload.get("profile") or payload.get("mode") or "atlas-smart").strip().lower() profile = (payload.get("profile") or payload.get("mode") or "atlas-quick").strip().lower()
conversation_id = payload.get("conversation_id") if isinstance(payload.get("conversation_id"), str) else "" conversation_id = payload.get("conversation_id") if isinstance(payload.get("conversation_id"), str) else ""
if not user_message: if not user_message:
return jsonify({"error": "message required"}), 400 return jsonify({"error": "message required"}), 400
started = time.time() started = time.time()
mode = "smart"
if profile in {"atlas-genius", "genius"}:
mode = "genius"
elif profile in {"atlas-quick", "quick"}:
mode = "quick" mode = "quick"
if profile in {"atlas-smart", "smart"}:
mode = "smart"
elif profile in {"atlas-genius", "genius"}:
mode = "genius"
reply = _atlasbot_answer(user_message, mode, conversation_id) reply = _atlasbot_answer(user_message, mode, conversation_id)
source = f"atlas-{mode}" source = f"atlas-{mode}"
if reply: if reply:
@ -67,7 +67,7 @@ def register(app) -> None:
def ai_info() -> Any: def ai_info() -> Any:
"""Return model and placement metadata for the requested AI profile.""" """Return model and placement metadata for the requested AI profile."""
profile = (request.args.get("profile") or "atlas-smart").strip().lower() profile = (request.args.get("profile") or "atlas-quick").strip().lower()
meta = _discover_ai_meta(profile) meta = _discover_ai_meta(profile)
return jsonify(meta) return jsonify(meta)

View File

@ -14,15 +14,6 @@ from .. import settings
_LAB_STATUS_CACHE: dict[str, Any] = {"ts": 0.0, "value": None} _LAB_STATUS_CACHE: dict[str, Any] = {"ts": 0.0, "value": None}
def _status_response(payload: dict[str, Any]) -> Any:
"""Return lab status JSON with browser and proxy caching disabled."""
response = jsonify(payload)
response.headers["Cache-Control"] = "no-store, max-age=0"
response.headers["Pragma"] = "no-cache"
return response
def _vm_query(expr: str) -> float | None: def _vm_query(expr: str) -> float | None:
"""Run one instant VictoriaMetrics query and return the largest value.""" """Run one instant VictoriaMetrics query and return the largest value."""
@ -180,7 +171,7 @@ def register(app) -> None:
now = time.time() now = time.time()
cached = _LAB_STATUS_CACHE.get("value") cached = _LAB_STATUS_CACHE.get("value")
if cached and (now - float(_LAB_STATUS_CACHE.get("ts", 0.0)) < settings.LAB_STATUS_CACHE_SEC): if cached and (now - float(_LAB_STATUS_CACHE.get("ts", 0.0)) < settings.LAB_STATUS_CACHE_SEC):
return _status_response(cached) return jsonify(cached)
t_total = time.perf_counter() t_total = time.perf_counter()
timings_ms: dict[str, int] = {} timings_ms: dict[str, int] = {}
@ -251,4 +242,4 @@ def register(app) -> None:
_LAB_STATUS_CACHE["ts"] = now _LAB_STATUS_CACHE["ts"] = now
_LAB_STATUS_CACHE["value"] = payload _LAB_STATUS_CACHE["value"] = payload
return _status_response(payload) return jsonify(payload)

View File

@ -80,7 +80,6 @@ KEYCLOAK_ADMIN_REALM = os.getenv("KEYCLOAK_ADMIN_REALM", KEYCLOAK_REALM)
ARIADNE_URL = os.getenv("ARIADNE_URL", "").strip() ARIADNE_URL = os.getenv("ARIADNE_URL", "").strip()
ARIADNE_TIMEOUT_SEC = float(os.getenv("ARIADNE_TIMEOUT_SEC", "10")) ARIADNE_TIMEOUT_SEC = float(os.getenv("ARIADNE_TIMEOUT_SEC", "10"))
ARIADNE_GAME_MODE_TIMEOUT_SEC = float(os.getenv("ARIADNE_GAME_MODE_TIMEOUT_SEC", "900"))
ARIADNE_RETRY_COUNT = int(os.getenv("ARIADNE_RETRY_COUNT", "2")) ARIADNE_RETRY_COUNT = int(os.getenv("ARIADNE_RETRY_COUNT", "2"))
ARIADNE_RETRY_BACKOFF_SEC = float(os.getenv("ARIADNE_RETRY_BACKOFF_SEC", "0.2")) ARIADNE_RETRY_BACKOFF_SEC = float(os.getenv("ARIADNE_RETRY_BACKOFF_SEC", "0.2"))

View File

@ -8,20 +8,13 @@ from atlas_portal.routes import account_wolf
class DummyAriadne: class DummyAriadne:
def __init__(self, enabled: bool = True) -> None: def __init__(self, enabled: bool = True) -> None:
self._enabled = enabled self._enabled = enabled
self.calls: list[tuple[str, str, object | None, dict | None, float | None]] = [] self.calls: list[tuple[str, str, object | None, dict | None]] = []
def enabled(self) -> bool: def enabled(self) -> bool:
return self._enabled return self._enabled
def proxy( def proxy(self, method: str, path: str, payload: object | None = None, params: dict | None = None):
self, self.calls.append((method, path, payload, params))
method: str,
path: str,
payload: object | None = None,
params: dict | None = None,
timeout_sec: float | None = None,
):
self.calls.append((method, path, payload, params, timeout_sec))
return jsonify({"path": path, "payload": payload, "params": params}) return jsonify({"path": path, "payload": payload, "params": params})
@ -49,7 +42,7 @@ def test_wolf_status_proxies_source_ip(monkeypatch) -> None:
) )
assert resp.status_code == 200 assert resp.status_code == 200
assert ariadne.calls == [("GET", "/api/game-stream/status", None, {"source_ip": "1.2.3.4"}, None)] assert ariadne.calls == [("GET", "/api/game-stream/status", None, {"source_ip": "1.2.3.4"})]
def test_wolf_status_prefers_public_query_ip(monkeypatch) -> None: def test_wolf_status_prefers_public_query_ip(monkeypatch) -> None:
@ -61,7 +54,7 @@ def test_wolf_status_prefers_public_query_ip(monkeypatch) -> None:
) )
assert resp.status_code == 200 assert resp.status_code == 200
assert ariadne.calls == [("GET", "/api/game-stream/status", None, {"source_ip": "181.1.87.186"}, None)] assert ariadne.calls == [("GET", "/api/game-stream/status", None, {"source_ip": "181.1.87.186"})]
def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None: def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None:
@ -75,7 +68,7 @@ def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None:
) )
assert resp.status_code == 200 assert resp.status_code == 200
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ttl_seconds": 120, "ip": "5.6.7.8"}, None, None)] assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ttl_seconds": 120, "ip": "5.6.7.8"}, None)]
def test_wolf_unlock_prefers_public_payload_ip(monkeypatch) -> None: def test_wolf_unlock_prefers_public_payload_ip(monkeypatch) -> None:
@ -88,7 +81,7 @@ def test_wolf_unlock_prefers_public_payload_ip(monkeypatch) -> None:
) )
assert resp.status_code == 200 assert resp.status_code == 200
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ip": "181.1.87.186"}, None, None)] assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ip": "181.1.87.186"}, None)]
def test_wolf_unlock_ignores_private_payload_ip(monkeypatch) -> None: def test_wolf_unlock_ignores_private_payload_ip(monkeypatch) -> None:
@ -101,7 +94,7 @@ def test_wolf_unlock_ignores_private_payload_ip(monkeypatch) -> None:
) )
assert resp.status_code == 200 assert resp.status_code == 200
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ip": "5.6.7.8"}, None, None)] assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ip": "5.6.7.8"}, None)]
def test_wolf_source_ip_prefers_nearest_public_proxy_value(monkeypatch) -> None: def test_wolf_source_ip_prefers_nearest_public_proxy_value(monkeypatch) -> None:
@ -115,7 +108,7 @@ def test_wolf_source_ip_prefers_nearest_public_proxy_value(monkeypatch) -> None:
) )
assert resp.status_code == 200 assert resp.status_code == 200
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ip": "181.1.87.186"}, None, None)] assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ip": "181.1.87.186"}, None)]
def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None: def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None:
@ -132,11 +125,11 @@ def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None:
client.post("/api/account/wolf/admin/firewall/unlock", json={"ip": "8.8.8.8", "target_user": "olya"}) client.post("/api/account/wolf/admin/firewall/unlock", json={"ip": "8.8.8.8", "target_user": "olya"})
assert ariadne.calls == [ assert ariadne.calls == [
("GET", "/api/game-stream/pairing/status", None, {"source_ip": "181.1.87.186"}, None), ("GET", "/api/game-stream/pairing/status", None, {"source_ip": "181.1.87.186"}),
("POST", "/api/game-stream/pairing/submit-pin", {"pair_secret": "secret", "pin": "1234", "source_ip": "181.1.87.186"}, None, None), ("POST", "/api/game-stream/pairing/submit-pin", {"pair_secret": "secret", "pin": "1234", "source_ip": "181.1.87.186"}, None),
("POST", "/api/admin/game-mode/start", {"game": "steam"}, None, account_wolf.settings.ARIADNE_GAME_MODE_TIMEOUT_SEC), ("POST", "/api/admin/game-mode/start", {"game": "steam"}, None),
("POST", "/api/admin/game-mode/stop", {"game": "steam"}, None, account_wolf.settings.ARIADNE_GAME_MODE_TIMEOUT_SEC), ("POST", "/api/admin/game-mode/stop", {"game": "steam"}, None),
("POST", "/api/admin/game-stream/firewall/unlock", {"ip": "8.8.8.8", "target_user": "olya"}, None, None), ("POST", "/api/admin/game-stream/firewall/unlock", {"ip": "8.8.8.8", "target_user": "olya"}, None),
] ]
@ -150,7 +143,7 @@ def test_wolf_user_revoke_accepts_public_payload_ip(monkeypatch) -> None:
) )
assert resp.status_code == 200 assert resp.status_code == 200
assert ariadne.calls == [("POST", "/api/game-stream/firewall/revoke", {"ip": "9.9.9.9"}, None, None)] assert ariadne.calls == [("POST", "/api/game-stream/firewall/revoke", {"ip": "9.9.9.9"}, None)]
def test_wolf_routes_require_account_and_ariadne(monkeypatch) -> None: def test_wolf_routes_require_account_and_ariadne(monkeypatch) -> None:

View File

@ -45,28 +45,12 @@ class AiRouteTests(TestCase):
self.assertEqual(data.get("source"), f"atlas-{expected_mode}") self.assertEqual(data.get("source"), f"atlas-{expected_mode}")
self.assertEqual(data.get("reply"), f"{expected_mode}:conv-{profile}") self.assertEqual(data.get("reply"), f"{expected_mode}:conv-{profile}")
resp = self.client.post(
"/api/chat",
data=json.dumps(
{
"message": "How is Titan doing?",
"conversation_id": "conv-default",
}
),
content_type="application/json",
)
data = resp.get_json()
self.assertEqual(resp.status_code, 200)
self.assertEqual(data.get("source"), "atlas-smart")
self.assertEqual(data.get("reply"), "smart:conv-default")
self.assertEqual( self.assertEqual(
seen, seen,
[ [
("quick", "conv-atlas-quick"), ("quick", "conv-atlas-quick"),
("smart", "conv-atlas-smart"), ("smart", "conv-atlas-smart"),
("genius", "conv-atlas-genius"), ("genius", "conv-atlas-genius"),
("smart", "conv-default"),
], ],
) )
@ -79,14 +63,6 @@ class AiRouteTests(TestCase):
self.assertEqual(data.get("profile"), "atlas-genius") self.assertEqual(data.get("profile"), "atlas-genius")
self.assertEqual(data.get("model"), "genius-model") self.assertEqual(data.get("model"), "genius-model")
with mock.patch.object(ai.settings, "AI_ATLASBOT_MODEL_SMART", "smart-model"):
resp = self.client.get("/api/ai/info")
data = resp.get_json()
self.assertEqual(resp.status_code, 200)
self.assertEqual(data.get("profile"), "atlas-smart")
self.assertEqual(data.get("model"), "smart-model")
def test_atlasbot_answer_uses_profile_specific_timeout(self): def test_atlasbot_answer_uses_profile_specific_timeout(self):
captured: dict[str, object] = {} captured: dict[str, object] = {}

View File

@ -129,8 +129,6 @@ def test_lab_status_uses_cache_and_probe_fallbacks(monkeypatch) -> None:
payload = response.get_json() payload = response.get_json()
assert response.status_code == 200 assert response.status_code == 200
assert response.headers["Cache-Control"] == "no-store, max-age=0"
assert response.headers["Pragma"] == "no-cache"
assert payload["connected"] is True assert payload["connected"] is True
assert payload["atlas"]["source"] == "grafana" assert payload["atlas"]["source"] == "grafana"
assert payload["dedicated_hosts"]["up"] is True assert payload["dedicated_hosts"]["up"] is True
@ -146,7 +144,6 @@ def test_lab_status_uses_cache_and_probe_fallbacks(monkeypatch) -> None:
second = client.get("/api/lab/status") second = client.get("/api/lab/status")
assert second.get_json() == payload assert second.get_json() == payload
assert second.headers["Cache-Control"] == "no-store, max-age=0"
lab._LAB_STATUS_CACHE["ts"] = 0.0 lab._LAB_STATUS_CACHE["ts"] = 0.0
lab._LAB_STATUS_CACHE["value"] = None lab._LAB_STATUS_CACHE["value"] = None

View File

@ -32,7 +32,7 @@
{ {
"@type": "Person", "@type": "Person",
"name": "Brad Stein", "name": "Brad Stein",
"jobTitle": "Senior SDET / DevOps Automation Engineer", "jobTitle": "DevOps Automation Engineer / Senior SDET",
"url": "https://bstein.dev/", "url": "https://bstein.dev/",
"email": "mailto:brad@bstein.dev", "email": "mailto:brad@bstein.dev",
"sameAs": ["https://www.linkedin.com/in/steinbradley/", "https://scm.bstein.dev/bstein"] "sameAs": ["https://www.linkedin.com/in/steinbradley/", "https://scm.bstein.dev/bstein"]

View File

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc"> <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
<title id="title">Brad Stein, Senior SDET and DevOps Automation Engineer</title> <title id="title">Brad Stein, DevOps Automation Engineer and Senior SDET</title>
<desc id="desc">A dark bstein.dev social card with professional engineering positioning and Titan Lab platform proof.</desc> <desc id="desc">A dark bstein.dev social card with professional engineering positioning and Titan Lab platform proof.</desc>
<defs> <defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1"> <linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
@ -20,7 +20,7 @@
<circle cx="880" cy="174" r="6" fill="#00e5c5"/> <circle cx="880" cy="174" r="6" fill="#00e5c5"/>
<text x="80" y="196" fill="#00e5c5" font-family="JetBrains Mono, monospace" font-size="26" letter-spacing="3">BSTEIN.DEV</text> <text x="80" y="196" fill="#00e5c5" font-family="JetBrains Mono, monospace" font-size="26" letter-spacing="3">BSTEIN.DEV</text>
<text x="80" y="286" fill="#f7fbff" font-family="Space Grotesk, Inter, sans-serif" font-size="74" font-weight="700">Brad Stein</text> <text x="80" y="286" fill="#f7fbff" font-family="Space Grotesk, Inter, sans-serif" font-size="74" font-weight="700">Brad Stein</text>
<text x="80" y="352" fill="#d7e5ff" font-family="Space Grotesk, Inter, sans-serif" font-size="40" font-weight="600">Senior SDET / DevOps Automation Engineer</text> <text x="80" y="352" fill="#d7e5ff" font-family="Space Grotesk, Inter, sans-serif" font-size="40" font-weight="600">DevOps Automation Engineer / Senior SDET</text>
<text x="80" y="422" fill="#9fb2d0" font-family="Space Grotesk, Inter, sans-serif" font-size="30">CI/CD · Kubernetes · Python · Linux · Observability</text> <text x="80" y="422" fill="#9fb2d0" font-family="Space Grotesk, Inter, sans-serif" font-size="30">CI/CD · Kubernetes · Python · Linux · Observability</text>
<text x="80" y="482" fill="#9fb2d0" font-family="Space Grotesk, Inter, sans-serif" font-size="26">Titan Lab is the live platform proof behind the work.</text> <text x="80" y="482" fill="#9fb2d0" font-family="Space Grotesk, Inter, sans-serif" font-size="26">Titan Lab is the live platform proof behind the work.</text>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -37,8 +37,7 @@ async function refreshLabStatus() {
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = window.setTimeout(() => controller.abort(), 10000); const timeoutId = window.setTimeout(() => controller.abort(), 10000);
try { try {
const resp = await fetch(`/api/lab/status?ts=${Date.now()}`, { const resp = await fetch("/api/lab/status", {
cache: "no-store",
headers: { Accept: "application/json" }, headers: { Accept: "application/json" },
signal: controller.signal, signal: controller.signal,
}); });
@ -46,14 +45,12 @@ async function refreshLabStatus() {
labStatus.value = await resp.json(); labStatus.value = await resp.json();
statusError.value = ""; statusError.value = "";
} catch (err) { } catch (err) {
if (!labStatus.value) {
labStatus.value = null; labStatus.value = null;
if (err?.name === "AbortError") { if (err?.name === "AbortError") {
statusError.value = "Live data timed out"; statusError.value = "Live data timed out";
} else { } else {
statusError.value = "Live data unavailable"; statusError.value = "Live data unavailable";
} }
}
} finally { } finally {
window.clearTimeout(timeoutId); window.clearTimeout(timeoutId);
statusLoading.value = false; statusLoading.value = false;
@ -72,7 +69,7 @@ onUnmounted(() => {
function scheduleNextPoll() { function scheduleNextPoll() {
if (pollTimerId) window.clearTimeout(pollTimerId); if (pollTimerId) window.clearTimeout(pollTimerId);
const delayMs = labStatus.value ? 30000 : 4000; const delayMs = labStatus.value ? 30000 : 8000;
pollTimerId = window.setTimeout(refreshLabStatus, delayMs); pollTimerId = window.setTimeout(refreshLabStatus, delayMs);
} }
</script> </script>

View File

@ -47,13 +47,8 @@ export function useWolfDashboard() {
sourceIp: "", sourceIp: "",
unlockTtlSeconds: 28800, unlockTtlSeconds: 28800,
gpuPriority: "unknown", gpuPriority: "unknown",
gpuOwner: "unknown",
gameModeStatus: "unknown", gameModeStatus: "unknown",
gameModeActive: false, gameModeActive: false,
localModel: "unknown",
localModelLoaded: false,
localInferenceReady: false,
inferencePath: "fallback",
selectedGame: "steam", selectedGame: "steam",
note: "", note: "",
manualIp: "", manualIp: "",
@ -87,14 +82,8 @@ export function useWolfDashboard() {
wolf.sourceIp = data.moonlight?.source_ip || ""; wolf.sourceIp = data.moonlight?.source_ip || "";
wolf.unlockTtlSeconds = Number(data.moonlight?.unlock_ttl_seconds || 28800); wolf.unlockTtlSeconds = Number(data.moonlight?.unlock_ttl_seconds || 28800);
wolf.gpuPriority = data.gpu?.priority || "unknown"; wolf.gpuPriority = data.gpu?.priority || "unknown";
const gameMode = data.gpu?.game_mode || {}; wolf.gameModeStatus = data.gpu?.game_mode?.status || "unknown";
wolf.gpuOwner = gameMode.gpu_owner || "unknown"; wolf.gameModeActive = Boolean(data.gpu?.game_mode?.active);
wolf.gameModeStatus = gameMode.status || "unknown";
wolf.gameModeActive = Boolean(gameMode.active);
wolf.localModel = gameMode.model || "unknown";
wolf.localModelLoaded = Boolean(gameMode.model_loaded);
wolf.localInferenceReady = Boolean(gameMode.local_inference_ready);
wolf.inferencePath = gameMode.inference_path || "fallback";
wolf.clients = Array.isArray(data.wolf?.clients) ? data.wolf.clients : []; wolf.clients = Array.isArray(data.wolf?.clients) ? data.wolf.clients : [];
wolf.pendingPairRequests = Array.isArray(data.wolf?.pending_pair_requests) ? data.wolf.pending_pair_requests : []; wolf.pendingPairRequests = Array.isArray(data.wolf?.pending_pair_requests) ? data.wolf.pending_pair_requests : [];
wolf.sessions = Array.isArray(data.wolf?.sessions) ? data.wolf.sessions : []; wolf.sessions = Array.isArray(data.wolf?.sessions) ? data.wolf.sessions : [];

View File

@ -1,223 +0,0 @@
<template>
<section id="contact" class="conversion-section" aria-labelledby="contact-title">
<div class="conversion-card final-contact">
<div>
<p class="section-kicker mono">Contact</p>
<h2 id="contact-title">{{ content.contactSection.heading }}</h2>
<p>{{ content.contactSection.description }}</p>
<p>
Recruiting for a remote engineering role? Send the role description, contract terms, location restrictions, and
expected timeline.
</p>
</div>
<div class="contact-actions action-card action-stack" aria-label="Contact actions">
<a class="btn primary-action" :href="mailtoHref">Email Brad</a>
<a class="btn secondary" :href="content.linkedInUrl" target="_blank" rel="noopener noreferrer">
Connect on LinkedIn
</a>
<button class="btn tertiary button-like" type="button" @click="copyEmail">
{{ emailCopied ? "Email Copied" : "Copy Email Address" }}
</button>
</div>
</div>
</section>
<footer class="site-footer">
<div>
<div class="footer-name">{{ content.professionalName }}</div>
<div>{{ content.role }}</div>
</div>
<nav aria-label="Footer navigation">
<a :href="mailtoHref">Email</a>
<a :href="content.linkedInUrl" target="_blank" rel="noopener noreferrer">LinkedIn</a>
<RouterLink :to="content.resumeUrl">R&eacute;sum&eacute;</RouterLink>
<a :href="content.sourceUrl" target="_blank" rel="noopener noreferrer">Public Source</a>
<a href="#platform">Platform</a>
<button type="button" @click="doLogin">Sign In</button>
<RouterLink to="/request-access">Request Lab Access</RouterLink>
</nav>
<div class="copyright mono">&copy; {{ currentYear }} Brad Stein</div>
</footer>
</template>
<script setup>
import { ref } from "vue";
import { RouterLink } from "vue-router";
import { login } from "../auth";
const props = defineProps({
content: Object,
mailtoHref: String,
currentYear: Number,
});
const emailCopied = ref(false);
function doLogin() {
login();
}
async function copyEmail() {
try {
await navigator.clipboard?.writeText(props.content.primaryContact.email);
emailCopied.value = true;
window.setTimeout(() => {
emailCopied.value = false;
}, 1500);
} catch {
emailCopied.value = false;
}
}
</script>
<style scoped>
.conversion-section {
box-sizing: border-box;
display: grid;
align-content: start;
scroll-margin-top: 0;
margin-top: 0;
padding: clamp(54px, 7vh, 82px) 0;
}
.conversion-card {
display: grid;
grid-template-columns: minmax(0, 1.3fr) minmax(300px, 0.7fr);
gap: 24px;
align-items: center;
padding: clamp(28px, 4vw, 42px);
}
.section-kicker {
color: var(--accent-cyan);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.06em;
margin: 0 0 8px;
}
.conversion-card h2 {
margin: 0;
font-size: clamp(30px, 5vw, 46px);
letter-spacing: 0;
}
.conversion-card p {
font-size: 17px;
line-height: 1.65;
}
.final-contact {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius);
background: linear-gradient(135deg, rgba(0, 229, 197, 0.08), rgba(120, 180, 255, 0.06));
padding: clamp(26px, 3.5vw, 38px);
}
#contact {
padding-top: clamp(42px, 6vh, 72px);
}
.contact-actions {
display: flex;
align-items: flex-start;
justify-content: flex-start;
}
.action-card {
padding: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
background: rgba(5, 12, 28, 0.55);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.action-stack {
width: 100%;
max-width: 360px;
justify-self: end;
flex-direction: column;
flex-wrap: nowrap;
gap: 12px;
}
.action-stack .btn {
width: 100%;
justify-content: center;
text-align: center;
}
.primary-action {
background: linear-gradient(135deg, rgba(0, 229, 197, 0.9), rgba(120, 180, 255, 0.82));
color: #02141d;
}
.tertiary {
background: transparent;
color: var(--text-strong);
}
.button-like {
cursor: pointer;
font: inherit;
}
.site-footer {
display: grid;
grid-template-columns: 1fr;
gap: 14px;
margin-top: 56px;
padding-top: 28px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
color: var(--text-muted);
}
.footer-name {
color: var(--text-strong);
font-weight: 800;
}
.site-footer nav {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.site-footer button {
border: 0;
padding: 0;
background: transparent;
color: var(--accent-cyan);
cursor: pointer;
font: inherit;
}
.btn:focus-visible,
.site-footer a:focus-visible,
.site-footer button:focus-visible {
outline: 2px solid rgba(0, 229, 197, 0.75);
outline-offset: 3px;
}
@media (max-width: 1080px) {
.conversion-card {
grid-template-columns: 1fr;
}
.conversion-section {
min-height: auto;
}
.action-stack {
justify-self: stretch;
max-width: none;
}
}
@media (max-width: 760px) {
.conversion-section {
margin-top: 0;
padding: 50px 0;
}
}
</style>

View File

@ -1,351 +0,0 @@
<template>
<section class="hero-section" aria-labelledby="home-title">
<div class="hero-copy">
<p class="eyebrow mono">{{ content.eyebrow }}</p>
<h1 id="home-title">{{ content.professionalName }}</h1>
<h2>{{ content.role }}</h2>
<p class="hero-summary">{{ content.summary }}</p>
<p class="experience-line">{{ content.experienceLine }}</p>
<p class="availability-line">{{ content.availability.label }}</p>
<div class="cta-row" aria-label="Professional actions">
<a class="btn primary-action" :href="mailtoHref">Discuss a Project</a>
<RouterLink class="btn secondary" :to="content.resumeUrl">View R&eacute;sum&eacute;</RouterLink>
<a class="btn tertiary" href="#platform">Explore the Live Platform</a>
</div>
<div class="tech-list" aria-label="Core technologies">
<span v-for="tech in content.techStack" :key="tech" class="pill mono">{{ tech }}</span>
</div>
</div>
<aside class="status-card glass" aria-labelledby="status-title">
<div class="status-head">
<p class="eyebrow mono">Live platform</p>
<h3 id="status-title">Titan Lab Status</h3>
</div>
<div class="status-list">
<div v-for="item in statusItems" :key="item.label" class="status-row">
<span class="status-dot" :class="item.state" aria-hidden="true"></span>
<div class="status-row-copy">
<a v-if="item.labelHref" class="status-label status-title-link" :href="item.labelHref">
{{ item.label }}
</a>
<div v-else class="status-label">{{ item.label }}</div>
<a
v-if="item.href && isExternal(item.href)"
class="status-value mono status-link"
:href="item.href"
target="_blank"
rel="noopener noreferrer"
>
{{ item.value }}
</a>
<RouterLink v-else-if="item.href" class="status-value mono status-link" :to="item.href">
{{ item.value }}
</RouterLink>
<div v-else class="status-value mono">{{ item.value }}</div>
<div v-if="item.children?.length" class="status-children" :aria-label="`${item.label} details`">
<div v-for="child in item.children" :key="child.label" class="status-child">
<span class="status-child-dot" :class="child.state" aria-hidden="true"></span>
<span class="status-child-label">{{ child.label }}</span>
<span class="status-child-value mono">{{ child.value }}</span>
</div>
</div>
</div>
</div>
</div>
<p class="status-note mono">Last checked: {{ lastCheckedLabel }}</p>
<p v-if="error || (!loading && !labStatus?.connected)" class="status-note">
Live status is temporarily unavailable. The platform overview remains available below.
</p>
</aside>
</section>
</template>
<script setup>
import { RouterLink } from "vue-router";
defineProps({
content: Object,
statusItems: Array,
labStatus: Object,
loading: Boolean,
error: String,
lastCheckedLabel: String,
mailtoHref: String,
});
function isExternal(href) {
return typeof href === "string" && /^https?:\/\//.test(href);
}
</script>
<style scoped>
.hero-section {
position: relative;
isolation: isolate;
display: grid;
grid-template-columns: minmax(0, 1.75fr) minmax(320px, 0.85fr);
gap: 26px;
align-items: center;
min-height: calc(100vh - 86px);
padding: 42px 0 30px;
overflow: hidden;
}
.hero-section::before,
.hero-section::after {
content: "";
position: absolute;
pointer-events: none;
z-index: -1;
}
.hero-section::before {
inset: 16% -8vw 8% 50%;
border-radius: 999px;
background:
radial-gradient(ellipse at 68% 42%, rgba(0, 229, 197, 0.17), transparent 32%),
radial-gradient(ellipse at 28% 66%, rgba(120, 180, 255, 0.11), transparent 36%),
radial-gradient(ellipse at center, rgba(11, 24, 46, 0.58), transparent 72%);
opacity: 0.8;
filter: blur(2px);
}
.hero-section::after {
width: min(300px, 24vw);
top: 28%;
right: 6vw;
bottom: 22%;
border-radius: 999px;
background:
radial-gradient(ellipse at center, rgba(0, 229, 197, 0.16), transparent 64%),
linear-gradient(90deg, transparent, rgba(120, 180, 255, 0.08), transparent);
opacity: 0.32;
filter: blur(18px);
}
.hero-copy,
.status-card {
position: relative;
z-index: 1;
}
.eyebrow {
color: var(--accent-cyan);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.06em;
margin: 0 0 8px;
}
.hero-copy h1 {
font-size: clamp(44px, 7vw, 72px);
line-height: 0.98;
margin: 0;
letter-spacing: 0;
}
.hero-copy h2 {
font-size: clamp(26px, 3.2vw, 36px);
margin: 12px 0 0;
color: var(--text-primary);
letter-spacing: 0;
}
.hero-summary {
max-width: 760px;
margin: 20px 0 0;
font-size: clamp(18px, 2.2vw, 22px);
line-height: 1.55;
color: var(--text-strong);
}
.experience-line,
.availability-line {
max-width: 760px;
margin: 14px 0 0;
font-size: 17px;
line-height: 1.65;
}
.availability-line {
color: rgba(170, 255, 215, 0.94);
}
.tech-list,
.cta-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.tech-list {
margin-top: 22px;
}
.cta-row {
margin-top: 28px;
}
.primary-action {
background: linear-gradient(135deg, rgba(0, 229, 197, 0.9), rgba(120, 180, 255, 0.82));
color: #02141d;
}
.tertiary {
background: transparent;
color: var(--text-strong);
}
.status-card {
padding: 20px;
min-width: 0;
}
.status-head h3 {
margin: 0 0 14px;
font-size: 28px;
}
.status-list {
display: grid;
gap: 14px;
}
.status-row {
display: grid;
grid-template-columns: 14px 1fr;
gap: 12px;
align-items: start;
padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.03);
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 999px;
margin-top: 4px;
background: var(--text-muted);
}
.status-dot.ok {
background: var(--accent-cyan);
}
.status-dot.warn {
background: #f2c94c;
}
.status-dot.bad {
background: var(--accent-rose);
}
.status-row-copy {
min-width: 0;
}
.status-label {
font-weight: 800;
color: var(--text-strong);
}
.status-title-link {
display: inline-flex;
text-decoration: none;
}
.status-title-link:hover {
color: var(--accent-cyan);
}
.status-value,
.status-note {
color: var(--text-muted);
}
.status-link {
display: inline-flex;
color: var(--accent-cyan);
}
.status-children {
display: grid;
gap: 6px;
margin-top: 10px;
}
.status-child {
display: grid;
grid-template-columns: 9px minmax(0, 1fr) auto;
gap: 8px;
align-items: center;
color: var(--text-muted);
font-size: 13px;
}
.status-child-dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: var(--text-muted);
}
.status-child-dot.ok {
background: var(--accent-cyan);
}
.status-child-dot.warn {
background: #f2c94c;
}
.status-child-dot.bad {
background: var(--accent-rose);
}
.status-child-value {
color: var(--text-strong);
font-size: 12px;
}
.status-note {
margin: 14px 0 0;
}
@media (min-width: 1180px) {
.hero-copy h2 {
white-space: nowrap;
}
}
@media (max-width: 1080px) {
.hero-section {
grid-template-columns: 1fr;
min-height: auto;
}
.hero-section::before {
inset: 96px -22vw 24px 18%;
opacity: 0.34;
}
.hero-section::after {
display: none;
}
}
@media (max-width: 760px) {
.hero-section {
padding-top: 28px;
}
.hero-section::before {
inset: 84px -44vw 18px 8%;
opacity: 0.28;
}
}
</style>

View File

@ -1,391 +0,0 @@
<template>
<section id="member-access" class="conversion-section" aria-labelledby="member-title">
<div class="conversion-card member-panel glass">
<div class="member-copy">
<p class="section-kicker mono">Member platform</p>
<h2 id="member-title">{{ content.memberAccessCopy.heading }}</h2>
<p>{{ content.memberAccessCopy.description }}</p>
<p v-if="memberPanelCopy" class="member-state-copy">{{ memberPanelCopy }}</p>
</div>
<div class="member-actions action-card" aria-live="polite" aria-label="Titan Lab member actions">
<div v-if="!auth.ready" class="member-loading">
<span class="skeleton"></span>
<span>Checking session...</span>
</div>
<template v-else-if="auth.authenticated">
<RouterLink class="btn primary-action" :to="memberAction?.to || '/apps'">
{{ memberAction?.label || "Open Services" }}
</RouterLink>
<RouterLink class="btn secondary" to="/account">Account</RouterLink>
<button class="btn tertiary button-like" type="button" @click="doLogout">Sign Out</button>
<p v-if="memberState.error" class="member-error">{{ memberState.error }}</p>
</template>
<template v-else>
<div class="member-action-group">
<button class="btn primary-action button-like" type="button" @click="doLogin">Sign In</button>
<RouterLink class="btn secondary" to="/request-access">Register</RouterLink>
</div>
<a v-if="auth.resetUrl" class="member-reset" :href="auth.resetUrl" target="_blank" rel="noopener noreferrer">
Trouble signing in? Reset password
</a>
</template>
</div>
<div class="member-service-row" aria-label="Member service access">
<div v-for="service in content.memberServices" :key="service.name" class="member-service-item">
<a
v-if="isExternal(service.href)"
class="member-service-link"
:href="service.href"
:aria-label="serviceTooltip(service)"
:title="serviceTooltip(service)"
target="_blank"
rel="noopener noreferrer"
>
<span class="member-service-icon" aria-hidden="true">{{ service.icon }}</span>
</a>
<RouterLink
v-else
class="member-service-link"
:to="service.href"
:aria-label="serviceTooltip(service)"
:title="serviceTooltip(service)"
>
<span class="member-service-icon" aria-hidden="true">{{ service.icon }}</span>
</RouterLink>
<span class="member-service-tooltip" aria-hidden="true">
<strong>{{ service.name }}</strong>
<span>{{ service.description }}</span>
</span>
</div>
</div>
</div>
</section>
</template>
<script setup>
import { RouterLink } from "vue-router";
import { auth, login, logout } from "../auth";
defineProps({
content: Object,
memberPanelCopy: String,
memberAction: Object,
memberState: Object,
});
function isExternal(href) {
return typeof href === "string" && /^https?:\/\//.test(href);
}
function serviceTooltip(service) {
return `${service.name}: ${service.description}`;
}
function doLogin() {
login();
}
function doLogout() {
logout();
}
</script>
<style scoped>
.conversion-section {
box-sizing: border-box;
display: grid;
align-content: start;
scroll-margin-top: 0;
margin-top: 0;
padding: clamp(54px, 7vh, 82px) 0;
}
.conversion-card {
display: grid;
grid-template-columns: minmax(0, 1.3fr) minmax(300px, 0.7fr);
gap: 24px;
align-items: center;
padding: clamp(28px, 4vw, 42px);
}
.section-kicker {
color: var(--accent-cyan);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.06em;
margin: 0 0 8px;
}
.conversion-card h2 {
margin: 0;
font-size: clamp(30px, 5vw, 46px);
letter-spacing: 0;
}
.conversion-card p {
font-size: 17px;
line-height: 1.65;
}
#member-access {
padding-top: clamp(40px, 5vh, 64px);
padding-bottom: clamp(34px, 5vh, 56px);
}
.member-panel {
grid-template-columns: minmax(0, 1fr) minmax(260px, 320px);
gap: 18px 28px;
align-items: center;
padding: clamp(22px, 3vw, 30px);
border-radius: 18px;
background:
linear-gradient(100deg, rgba(255, 255, 255, 0.045), rgba(5, 12, 28, 0.72)),
rgba(255, 255, 255, 0.025);
}
.member-panel h2 {
font-size: clamp(28px, 4vw, 42px);
}
.member-panel p {
max-width: 740px;
margin: 10px 0 0;
}
.member-copy {
min-width: 0;
}
.member-service-row {
position: relative;
grid-column: 1 / -1;
display: flex;
flex-wrap: nowrap;
justify-content: center;
gap: 7px;
width: 100%;
max-width: none;
margin-top: 0;
padding-top: 14px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.member-service-item {
position: relative;
flex: 0 0 auto;
}
.member-service-link {
width: 38px;
height: 38px;
display: grid;
place-items: center;
border-radius: 8px;
border: 1px solid rgba(0, 229, 197, 0.22);
background:
linear-gradient(135deg, rgba(0, 229, 197, 0.08), rgba(120, 180, 255, 0.06)),
rgba(255, 255, 255, 0.035);
text-decoration: none;
transition:
border-color 160ms ease,
background 160ms ease,
transform 160ms ease;
}
.member-service-link:hover,
.member-service-link:focus-visible {
border-color: rgba(0, 229, 197, 0.58);
background:
linear-gradient(135deg, rgba(0, 229, 197, 0.16), rgba(120, 180, 255, 0.12)),
rgba(255, 255, 255, 0.055);
transform: translateY(-2px);
}
.member-service-icon {
font-size: 19px;
line-height: 1;
}
.member-service-tooltip {
position: absolute;
left: 50%;
bottom: calc(100% + 10px);
z-index: 30;
width: max-content;
max-width: min(250px, 74vw);
display: grid;
gap: 4px;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid rgba(0, 229, 197, 0.22);
background: rgba(4, 11, 24, 0.96);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
color: var(--text-primary);
font-size: 13px;
line-height: 1.35;
opacity: 0;
pointer-events: none;
transform: translate(-50%, 4px);
visibility: hidden;
transition:
opacity 140ms ease,
transform 140ms ease,
visibility 140ms ease;
}
.member-service-tooltip::after {
content: "";
position: absolute;
left: 50%;
top: 100%;
width: 9px;
height: 9px;
border-right: 1px solid rgba(0, 229, 197, 0.22);
border-bottom: 1px solid rgba(0, 229, 197, 0.22);
background: rgba(4, 11, 24, 0.96);
transform: translate(-50%, -5px) rotate(45deg);
}
.member-service-tooltip strong {
color: var(--text-strong);
}
.member-service-tooltip span {
color: var(--text-muted);
}
.member-service-item:hover .member-service-tooltip,
.member-service-item:focus-within .member-service-tooltip {
opacity: 1;
transform: translate(-50%, 0);
visibility: visible;
}
.member-state-copy {
color: var(--text-muted);
}
.member-actions {
display: flex;
align-items: flex-start;
justify-content: flex-start;
flex-direction: column;
flex-wrap: nowrap;
gap: 12px;
width: 100%;
max-width: 360px;
justify-self: end;
}
.member-action-group {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
align-items: center;
width: 100%;
}
.action-card {
padding: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
background: rgba(5, 12, 28, 0.55);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.member-action-group .btn {
width: 100%;
justify-content: center;
text-align: center;
}
.primary-action {
background: linear-gradient(135deg, rgba(0, 229, 197, 0.9), rgba(120, 180, 255, 0.82));
color: #02141d;
}
.tertiary {
background: transparent;
color: var(--text-strong);
}
.button-like {
cursor: pointer;
font: inherit;
}
.member-loading {
min-height: 44px;
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--text-muted);
}
.skeleton {
width: 96px;
height: 18px;
border-radius: 999px;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.05));
}
.member-reset,
.member-error {
width: 100%;
}
.member-reset {
color: var(--accent-cyan);
font-size: 15px;
text-align: center;
}
.member-service-link:focus-visible {
outline: 2px solid rgba(0, 229, 197, 0.75);
outline-offset: 3px;
}
@media (max-width: 1080px) {
.conversion-card,
.member-panel {
grid-template-columns: 1fr;
}
.conversion-section {
min-height: auto;
}
.member-actions {
justify-self: stretch;
max-width: none;
}
}
@media (max-width: 760px) {
.conversion-section {
margin-top: 0;
padding: 50px 0;
}
.member-service-row {
justify-content: flex-start;
gap: 5px;
overflow-x: auto;
padding-bottom: 6px;
scrollbar-width: thin;
}
.member-service-link {
width: 34px;
height: 34px;
}
.member-service-icon {
font-size: 17px;
}
}
</style>

View File

@ -1,398 +0,0 @@
<template>
<section id="capabilities" class="content-section" aria-labelledby="capabilities-title">
<div class="section-heading">
<p class="section-kicker mono">{{ content.capabilitySection.kicker }}</p>
<h2 id="capabilities-title">{{ content.capabilitySection.heading }}</h2>
<p>{{ content.capabilitySection.description }}</p>
</div>
<div class="card-grid">
<article v-for="capability in content.capabilities" :key="capability.title" class="info-card">
<div class="icon-badge mono" aria-hidden="true">{{ capability.icon }}</div>
<h3>{{ capability.title }}</h3>
<p>{{ capability.description }}</p>
</article>
</div>
</section>
<section id="services" class="content-section" aria-labelledby="services-title">
<div class="section-heading">
<p class="section-kicker mono">{{ content.serviceSection.kicker }}</p>
<h2 id="services-title">{{ content.serviceSection.heading }}</h2>
<p>{{ content.serviceSection.description }}</p>
</div>
<div class="service-list">
<article v-for="service in content.services" :key="service.title" class="service-card">
<h3>{{ service.title }}</h3>
<p><strong>Build path:</strong> {{ service.investigation }}</p>
<p><strong>Outcome:</strong> {{ service.outcome }}</p>
</article>
</div>
</section>
<section id="work" class="content-section" aria-labelledby="work-title">
<div class="section-heading">
<p class="section-kicker mono">{{ content.workSection.kicker }}</p>
<h2 id="work-title">{{ content.workSection.heading }}</h2>
<p>{{ content.workSection.description }}</p>
</div>
<div class="work-grid">
<article
v-for="item in content.selectedWork"
:key="item.title"
class="work-card card"
:class="{ minor: item.weight === 'minor' }"
>
<div class="work-card-top">
<div class="work-icon mono" aria-hidden="true">{{ item.icon }}</div>
<div>
<p class="work-kind mono">{{ item.kind }}</p>
<h3>{{ item.title }}</h3>
</div>
</div>
<p>{{ item.description }}</p>
<div v-if="item.points?.length || item.links?.length" class="work-proof">
<div v-if="item.points?.length" class="work-points" aria-label="Selected proof points">
<span v-for="point in item.points" :key="point" class="pill mono">{{ point }}</span>
</div>
<div v-if="item.links?.length" class="work-links">
<a
v-for="link in item.links"
:key="link.href"
class="work-link mono"
:href="link.href"
target="_blank"
rel="noopener noreferrer"
>
{{ link.label }}
</a>
</div>
</div>
</article>
</div>
</section>
<section id="engagement" class="content-section" aria-labelledby="engagement-title">
<div class="section-heading centered">
<p class="section-kicker mono">Engagement</p>
<h2 id="engagement-title">Ways to Work Together</h2>
<p>
Fixed-scope and contract pricing is based on the problem, access requirements, urgency, and expected deliverables.
</p>
</div>
<div class="engagement-grid">
<article v-for="item in content.engagementTypes" :key="item.title" class="info-card">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
<div class="duration mono">{{ item.duration }}</div>
</article>
</div>
</section>
<section id="process" class="content-section" aria-labelledby="process-title">
<div class="section-heading">
<p class="section-kicker mono">Process</p>
<h2 id="process-title">A Practical Engagement Process</h2>
</div>
<ol class="process-list">
<li v-for="(step, index) in content.processSteps" :key="step.title" class="process-step">
<span class="step-number mono">{{ index + 1 }}</span>
<div>
<h3>{{ step.title }}</h3>
<p>{{ step.description }}</p>
</div>
</li>
</ol>
<p class="safety-note">
Do not email passwords, private keys, tokens, customer data, or other secrets. Secure access can be arranged after
scope is confirmed.
</p>
</section>
</template>
<script setup>
defineProps({
content: Object,
});
</script>
<style scoped>
.content-section {
box-sizing: border-box;
display: grid;
align-content: start;
scroll-margin-top: 0;
margin-top: 0;
padding: clamp(58px, 7vh, 88px) 0;
}
.section-kicker {
color: var(--accent-cyan);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.06em;
margin: 0 0 8px;
}
.section-heading {
max-width: 820px;
margin-bottom: 22px;
}
.section-heading.centered {
width: min(100%, 920px);
margin-left: auto;
margin-right: auto;
}
.section-heading h2 {
margin: 0;
font-size: clamp(30px, 5vw, 46px);
letter-spacing: 0;
}
.section-heading p {
font-size: 17px;
line-height: 1.65;
}
.card-grid,
.work-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.work-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.engagement-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
width: min(100%, 920px);
margin: 0 auto;
}
.info-card,
.service-card {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
padding: 18px;
background: rgba(255, 255, 255, 0.035);
}
.work-card {
position: relative;
overflow: hidden;
padding: 16px;
display: flex;
flex-direction: column;
}
.work-card::after {
content: "";
position: absolute;
inset: auto 0 0;
height: 3px;
background: linear-gradient(90deg, rgba(0, 229, 197, 0.7), rgba(120, 180, 255, 0.4), transparent);
opacity: 0.5;
}
.work-card.minor {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(0, 0.7fr) minmax(280px, 1fr) minmax(250px, 0.8fr);
gap: 14px 22px;
align-items: start;
}
.work-card-top {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 14px;
}
.work-icon {
width: 46px;
height: 46px;
flex: 0 0 auto;
display: grid;
place-items: center;
border-radius: 14px;
border: 1px solid rgba(0, 229, 197, 0.25);
color: var(--accent-cyan);
background:
linear-gradient(135deg, rgba(0, 229, 197, 0.1), rgba(120, 180, 255, 0.08)),
rgba(255, 255, 255, 0.035);
}
.work-kind {
margin: 0 0 4px;
color: var(--accent-cyan);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.info-card h3,
.service-card h3,
.work-card h3,
.process-step h3 {
margin: 0 0 8px;
letter-spacing: 0;
}
.info-card p,
.service-card p,
.work-card p,
.process-step p {
line-height: 1.6;
}
.work-card p {
color: var(--text-muted);
font-size: 15px;
}
.work-proof {
display: grid;
gap: 10px;
margin-top: auto;
padding-top: 12px;
}
.work-links,
.work-points {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 0;
}
.work-points {
gap: 7px;
}
.work-points .pill {
min-height: 30px;
padding: 5px 9px;
font-size: 12px;
}
.work-link {
min-height: 32px;
display: inline-flex;
align-items: center;
padding: 6px 10px;
border-radius: 10px;
border: 1px solid rgba(0, 229, 197, 0.22);
color: var(--accent-cyan);
text-decoration: none;
background: rgba(0, 229, 197, 0.05);
font-size: 12px;
}
.work-card.minor .work-card-top {
margin-bottom: 0;
}
.work-card.minor .work-proof {
padding-top: 0;
}
.icon-badge {
width: 40px;
height: 40px;
display: grid;
place-items: center;
border-radius: 10px;
border: 1px solid rgba(0, 229, 197, 0.22);
color: var(--accent-cyan);
background: rgba(0, 229, 197, 0.06);
margin-bottom: 12px;
}
.service-list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
.service-card strong {
color: var(--text-strong);
}
.duration {
color: var(--accent-cyan);
margin-top: 12px;
font-size: 13px;
}
.process-list {
list-style: none;
padding: 0;
margin: 0;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 14px;
}
.process-step {
display: grid;
gap: 12px;
align-content: start;
padding: 18px;
border-radius: var(--radius-sm);
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.035);
}
.step-number {
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 50%;
background: rgba(0, 229, 197, 0.1);
border: 1px solid rgba(0, 229, 197, 0.22);
color: var(--accent-cyan);
}
.safety-note {
margin-top: 16px;
padding: 14px 16px;
border-left: 3px solid rgba(255, 220, 120, 0.45);
background: rgba(255, 220, 120, 0.05);
}
@media (max-width: 1080px) {
.card-grid,
.work-grid,
.service-list,
.engagement-grid,
.process-list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 760px) {
.card-grid,
.engagement-grid,
.work-grid,
.service-list,
.process-list {
grid-template-columns: 1fr;
}
.content-section {
margin-top: 0;
padding: 50px 0;
}
.work-card.minor {
grid-template-columns: 1fr;
}
}
</style>

View File

@ -350,7 +350,7 @@ function pickIcon(name) {
if (h.includes("translation")) return "TR"; if (h.includes("translation")) return "TR";
if (h.includes("grafana")) return "GF"; if (h.includes("grafana")) return "GF";
if (h.includes("pegasus")) return "PG"; if (h.includes("pegasus")) return "PG";
if (h.includes("cassandra")) return "CA"; if (h.includes("veles")) return "VL";
if (h.includes("ai chat")) return "AI"; if (h.includes("ai chat")) return "AI";
if (h.includes("ai image") || h.includes("vision")) return "VI"; if (h.includes("ai image") || h.includes("vision")) return "VI";
if (h.includes("ai speech")) return "SP"; if (h.includes("ai speech")) return "SP";
@ -366,7 +366,7 @@ function serviceRank(service) {
"Flux", "Flux",
"Grafana", "Grafana",
"OpenSearch", "OpenSearch",
"Cassandra", "Veles",
"Nextcloud", "Nextcloud",
"Outline", "Outline",
"Planka", "Planka",

View File

@ -1,7 +1,7 @@
<template> <template>
<header class="topbar"> <header class="topbar">
<a class="skip-link" href="#main-content">Skip to content</a> <a class="skip-link" href="#main-content">Skip to content</a>
<RouterLink class="profile" :to="homePath" @click="closeMenu"> <RouterLink class="profile" to="/" @click="closeMenu">
<div class="avatar" aria-hidden="true"> <div class="avatar" aria-hidden="true">
<img src="@/assets/profile-avatar.jpg" alt="" /> <img src="@/assets/profile-avatar.jpg" alt="" />
</div> </div>
@ -36,20 +36,7 @@
<a v-for="link in sectionLinks" :key="link.href" class="nav-link" :href="link.href" @click="closeMenu"> <a v-for="link in sectionLinks" :key="link.href" class="nav-link" :href="link.href" @click="closeMenu">
{{ link.label }} {{ link.label }}
</a> </a>
<a class="nav-link professional-action" :href="`${homePath}#contact`" @click="closeMenu">Contact</a> <a class="nav-link professional-action" href="/#contact" @click="closeMenu">Contact</a>
<div class="mode-switch" aria-label="Homepage mode">
<RouterLink
v-for="mode in homepageModes"
:key="mode.id"
class="nav-link mode-link"
:class="{ active: mode.id === currentMode }"
:to="mode.path"
@click="closeMenu"
>
{{ mode.label }}
</RouterLink>
</div>
<div class="utility"> <div class="utility">
<template v-if="!auth.ready"> <template v-if="!auth.ready">
@ -75,29 +62,21 @@
</template> </template>
<script setup> <script setup>
import { computed, nextTick, onUnmounted, ref, watch } from "vue"; import { nextTick, onUnmounted, ref, watch } from "vue";
import { RouterLink, useRoute } from "vue-router"; import { RouterLink } from "vue-router";
import { auth, login, logout } from "@/auth"; import { auth, login, logout } from "@/auth";
import { import { homepageContent } from "@/data/homepageContent";
homepageContentForMode,
homepageModeFromRoute,
homepageModes,
homepagePathForMode,
} from "@/data/homepageContent";
import { useMemberAccessState } from "@/member/useMemberAccessState"; import { useMemberAccessState } from "@/member/useMemberAccessState";
const { memberAction } = useMemberAccessState(); const { memberAction } = useMemberAccessState();
const route = useRoute(); const content = homepageContent;
const currentMode = computed(() => homepageModeFromRoute(route));
const content = computed(() => homepageContentForMode(currentMode.value));
const homePath = computed(() => homepagePathForMode(currentMode.value));
const sectionLinks = computed(() => [ const sectionLinks = [
{ label: "Work", href: `${homePath.value}#work` }, { label: "Work", href: "/#work" },
{ label: "Capabilities", href: `${homePath.value}#capabilities` }, { label: "Capabilities", href: "/#capabilities" },
{ label: "Platform", href: `${homePath.value}#platform` }, { label: "Platform", href: "/#platform" },
{ label: "Process", href: `${homePath.value}#process` }, { label: "Process", href: "/#process" },
]); ];
const menuOpen = ref(false); const menuOpen = ref(false);
const navEl = ref(null); const navEl = ref(null);
@ -124,10 +103,6 @@ function onWindowKeydown(event) {
menuButton.value?.focus(); menuButton.value?.focus();
} }
/**
* WHY: Keep the opened mobile menu keyboard-contained and return Escape focus to the menu toggle.
* @param {KeyboardEvent} event Navigation key event from the primary nav.
*/
function onNavKeydown(event) { function onNavKeydown(event) {
if (event.key === "Escape") { if (event.key === "Escape") {
closeMenu(); closeMenu();
@ -260,15 +235,6 @@ onUnmounted(() => {
border-left: 1px solid rgba(255, 255, 255, 0.08); border-left: 1px solid rgba(255, 255, 255, 0.08);
} }
.mode-switch {
display: flex;
align-items: center;
gap: 4px;
margin-left: 4px;
padding-left: 10px;
border-left: 1px solid rgba(255, 255, 255, 0.08);
}
.nav-link { .nav-link {
min-height: 44px; min-height: 44px;
display: inline-flex; display: inline-flex;
@ -283,18 +249,6 @@ onUnmounted(() => {
white-space: nowrap; white-space: nowrap;
} }
.mode-link {
min-height: 36px;
padding: 7px 9px;
color: var(--text-muted);
}
.mode-link.active {
color: var(--accent-cyan);
border-color: rgba(0, 229, 197, 0.28);
background: rgba(0, 229, 197, 0.07);
}
.button { .button {
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
@ -382,16 +336,6 @@ onUnmounted(() => {
flex-direction: column; flex-direction: column;
} }
.mode-switch {
margin-left: 0;
padding-left: 0;
padding-top: 10px;
border-left: 0;
border-top: 1px solid rgba(255, 255, 255, 0.08);
align-items: stretch;
flex-direction: column;
}
.nav-link { .nav-link {
justify-content: flex-start; justify-content: flex-start;
white-space: normal; white-space: normal;

View File

@ -35,12 +35,6 @@
<span class="k mono">GPU</span> <span class="k mono">GPU</span>
<strong class="mono">{{ wolf.gpuPriority }}</strong> <strong class="mono">{{ wolf.gpuPriority }}</strong>
</div> </div>
<div class="wolf-summary-item">
<span class="k mono">Hermes</span>
<strong class="mono" :class="wolf.localInferenceReady ? 'ok-text' : 'warn-text'">
{{ wolf.inferencePath }}
</strong>
</div>
<div class="wolf-summary-item"> <div class="wolf-summary-item">
<span class="k mono">Firewall</span> <span class="k mono">Firewall</span>
<strong class="mono" :class="currentIpUnlocked ? 'ok-text' : 'warn-text'"> <strong class="mono" :class="currentIpUnlocked ? 'ok-text' : 'warn-text'">
@ -66,14 +60,6 @@
<span class="k mono">Paired devices</span> <span class="k mono">Paired devices</span>
<span class="v mono">{{ pairedDeviceNames }}</span> <span class="v mono">{{ pairedDeviceNames }}</span>
</div> </div>
<div class="row">
<span class="k mono">GPU owner</span>
<span class="v mono">{{ wolf.gpuOwner }}</span>
</div>
<div class="row">
<span class="k mono">Local model</span>
<span class="v mono">{{ wolf.localModel }} · {{ wolf.localModelLoaded ? "loaded" : "unloaded" }}</span>
</div>
</div> </div>
<div class="actions"> <div class="actions">
@ -107,7 +93,7 @@
<div v-if="wolf.canControlGpu" class="secret-box"> <div v-if="wolf.canControlGpu" class="secret-box">
<div class="secret-head"> <div class="secret-head">
<div class="pill mono">Admin</div> <div class="pill mono">Admin</div>
<span class="hint mono">RTX 3080 checkout</span> <span class="hint mono">GPU priority</span>
</div> </div>
<div class="wolf-controls"> <div class="wolf-controls">
<select v-model="wolf.selectedGame" class="input mono"> <select v-model="wolf.selectedGame" class="input mono">
@ -115,19 +101,15 @@
<option value="arc-raiders">Arc Raiders</option> <option value="arc-raiders">Arc Raiders</option>
<option value="satisfactory">Satisfactory</option> <option value="satisfactory">Satisfactory</option>
<option value="wolf">Wolf</option> <option value="wolf">Wolf</option>
<option value="desktop">Desktop + Wolf</option>
</select> </select>
<input v-model="wolf.note" class="input mono" type="text" placeholder="note" /> <input v-model="wolf.note" class="input mono" type="text" placeholder="note" />
<button class="primary" type="button" :disabled="Boolean(wolf.actioning)" @click="$emit('game-mode', 'start')"> <button class="primary" type="button" :disabled="Boolean(wolf.actioning)" @click="$emit('game-mode', 'start')">
{{ wolf.actioning === "start" ? "Reserving..." : "Reserve RTX 3080" }} {{ wolf.actioning === "start" ? "Starting..." : "Prioritize Wolf" }}
</button> </button>
<button class="copy mono" type="button" :disabled="Boolean(wolf.actioning)" @click="$emit('game-mode', 'stop')"> <button class="copy mono" type="button" :disabled="Boolean(wolf.actioning)" @click="$emit('game-mode', 'stop')">
{{ wolf.actioning === "stop" ? "Releasing..." : "Release for images" }} {{ wolf.actioning === "stop" ? "Stopping..." : "Restore AI" }}
</button> </button>
</div> </div>
<div class="hint mono">
Desktop and Wolf may share this reservation. Local image generation waits until it is released.
</div>
<div class="wolf-controls manual-unlock"> <div class="wolf-controls manual-unlock">
<input v-model="wolf.manualIp" class="input mono" type="text" placeholder="IP address" /> <input v-model="wolf.manualIp" class="input mono" type="text" placeholder="IP address" />
<input v-model="wolf.manualUser" class="input mono" type="text" placeholder="user" /> <input v-model="wolf.manualUser" class="input mono" type="text" placeholder="user" />

View File

@ -1,4 +1,4 @@
const focusMode = (import.meta.env?.VITE_HOMEPAGE_FOCUS || "sdet").trim().toLowerCase(); const focusMode = (import.meta.env?.VITE_HOMEPAGE_FOCUS || "devops").trim().toLowerCase();
const focusProfiles = { const focusProfiles = {
devops: { devops: {
@ -6,27 +6,21 @@ const focusProfiles = {
role: "DevOps Automation Engineer / Senior SDET", role: "DevOps Automation Engineer / Senior SDET",
shortRole: "DevOps Automation / Platform", shortRole: "DevOps Automation / Platform",
summary: summary:
"I build custom software, tooling, and infrastructure that make development and test workflows easier, safer, and more repeatable: platform tools, CI/CD, Docker, Kubernetes, Linux systems, Python automation, observability, and release gates.", "I build and repair the machinery that gets code safely into production: CI/CD, Docker, Kubernetes, Linux infrastructure, Python automation, observability, and test gates.",
experienceLine: experienceLine:
"Engineering experience across Boeing, IBM, deployment automation, infrastructure tooling, system testing, and production-minded platform operations.", "Engineering experience across Boeing, IBM, deployment automation, infrastructure tooling, system testing, and production-minded platform operations.",
techStack: [ techStack: [
"Kubernetes", "Jenkins",
"Docker", "Docker",
"Kubernetes",
"Linux", "Linux",
"Terraform", "Terraform",
"Ansible",
"Flux", "Flux",
"Helm",
"Jenkins",
"Harbor",
"Keycloak",
"Vault",
"Grafana", "Grafana",
"Prometheus", "Prometheus",
"Python", "Python",
"Bash", "Bash",
"Pytest", "Pytest",
"Selenium",
"Playwright", "Playwright",
], ],
capabilityOrder: [ capabilityOrder: [
@ -63,7 +57,7 @@ const focusProfiles = {
description: description:
"Inspectable repos, live services, and professional work samples that show how I approach real infrastructure.", "Inspectable repos, live services, and professional work samples that show how I approach real infrastructure.",
}, },
selectedWorkOrder: ["Titan Lab + titan-iac", "Atlas operator tools", "IBM", "Boeing", "Cassandra"], selectedWorkOrder: ["Titan Lab + titan-iac", "Atlas operator tools", "IBM", "Boeing", "Veles"],
engagementOrder: [ engagementOrder: [
"Fixed-Scope Technical Rescue", "Fixed-Scope Technical Rescue",
"Short-Term Remote Contract", "Short-Term Remote Contract",
@ -75,7 +69,7 @@ const focusProfiles = {
description: description:
"Send the problem, technical stack, expected outcome, and timeline. I will respond with whether it is a fit, what information is needed, and the most practical next step.", "Send the problem, technical stack, expected outcome, and timeline. I will respond with whether it is a fit, what information is needed, and the most practical next step.",
}, },
cassandra: { veles: {
description: description:
"Homegrown AI playtester for Magic: The Gathering deck construction, deployed for the community on Atlas with backend orchestration and disposable simulation workers.", "Homegrown AI playtester for Magic: The Gathering deck construction, deployed for the community on Atlas with backend orchestration and disposable simulation workers.",
points: ["community AI service", "Kubernetes deployment", "worker orchestration", "job cleanup"], points: ["community AI service", "Kubernetes deployment", "worker orchestration", "job cleanup"],
@ -129,15 +123,14 @@ const focusProfiles = {
"Pytest", "Pytest",
"Selenium", "Selenium",
"Playwright", "Playwright",
"API Testing", "API testing",
"E2E Testing",
"CI/CD",
"Jenkins", "Jenkins",
"Docker", "Docker",
"Kubernetes", "Kubernetes",
"Linux", "Linux",
"Grafana", "Grafana",
"Prometheus", "Prometheus",
"Terraform",
], ],
capabilityOrder: [ capabilityOrder: [
"Test Automation & SDET", "Test Automation & SDET",
@ -173,7 +166,7 @@ const focusProfiles = {
description: description:
"Engineering work across system testing, cloud validation, delivery gates, and live platform operations.", "Engineering work across system testing, cloud validation, delivery gates, and live platform operations.",
}, },
selectedWorkOrder: ["Boeing", "IBM", "Titan Lab + titan-iac", "Atlas operator tools", "Cassandra"], selectedWorkOrder: ["Boeing", "IBM", "Titan Lab + titan-iac", "Atlas operator tools", "Veles"],
engagementOrder: [ engagementOrder: [
"Short-Term Remote Contract", "Short-Term Remote Contract",
"Fixed-Scope Technical Rescue", "Fixed-Scope Technical Rescue",
@ -185,7 +178,7 @@ const focusProfiles = {
description: description:
"Send the problem, technical stack, expected outcome, and timeline. I will respond with whether it is a fit, what information is needed, and the most practical next step.", "Send the problem, technical stack, expected outcome, and timeline. I will respond with whether it is a fit, what information is needed, and the most practical next step.",
}, },
cassandra: { veles: {
description: description:
"Homegrown AI playtester with simulation flows, backend service boundaries, and useful surfaces for API, worker, regression, and result-quality testing.", "Homegrown AI playtester with simulation flows, backend service boundaries, and useful surfaces for API, worker, regression, and result-quality testing.",
points: ["simulation flows", "API coverage", "regression surfaces", "result checks"], points: ["simulation flows", "API coverage", "regression surfaces", "result checks"],
@ -226,7 +219,7 @@ const focusProfiles = {
], ],
}, },
}, },
developer: { dev: {
eyebrow: "AVAILABLE FOR REMOTE PRODUCT, PLATFORM, AND AI SERVICE WORK", eyebrow: "AVAILABLE FOR REMOTE PRODUCT, PLATFORM, AND AI SERVICE WORK",
role: "Full-Stack Platform Engineer / Senior SDET", role: "Full-Stack Platform Engineer / Senior SDET",
shortRole: "Product / Platform Engineering", shortRole: "Product / Platform Engineering",
@ -237,17 +230,15 @@ const focusProfiles = {
techStack: [ techStack: [
"Vue", "Vue",
"Python", "Python",
"Flask", "AI integration",
"API Design", "API design",
"PostgreSQL",
"Redis",
"Workers",
"AI Integration",
"Docker", "Docker",
"Kubernetes", "Kubernetes",
"CI/CD", "Jenkins",
"Flux",
"Grafana",
"PostgreSQL",
"Pytest", "Pytest",
"Selenium",
"Playwright", "Playwright",
], ],
capabilityOrder: [ capabilityOrder: [
@ -284,7 +275,7 @@ const focusProfiles = {
description: description:
"Inspectable repos, live services, and professional work samples that show product engineering backed by real operations.", "Inspectable repos, live services, and professional work samples that show product engineering backed by real operations.",
}, },
selectedWorkOrder: ["Cassandra", "Titan Lab + titan-iac", "Atlas operator tools", "IBM", "Boeing"], selectedWorkOrder: ["Veles", "Titan Lab + titan-iac", "Atlas operator tools", "IBM", "Boeing"],
engagementOrder: [ engagementOrder: [
"Short-Term Remote Contract", "Short-Term Remote Contract",
"Fixed-Scope Technical Rescue", "Fixed-Scope Technical Rescue",
@ -296,7 +287,7 @@ const focusProfiles = {
description: description:
"Send the idea or problem, technical stack, expected outcome, and timeline. I will respond with whether it is a fit, what information is needed, and the most practical next step.", "Send the idea or problem, technical stack, expected outcome, and timeline. I will respond with whether it is a fit, what information is needed, and the most practical next step.",
}, },
cassandra: { veles: {
description: description:
"Homegrown AI service for Magic: The Gathering deck construction, with product workflow, AI integration, backend orchestration, content seeding, and disposable simulation jobs.", "Homegrown AI service for Magic: The Gathering deck construction, with product workflow, AI integration, backend orchestration, content seeding, and disposable simulation jobs.",
points: ["AI integration", "product workflow", "service boundaries", "simulation workers"], points: ["AI integration", "product workflow", "service boundaries", "simulation workers"],
@ -324,7 +315,7 @@ const focusProfiles = {
}, },
{ {
label: "Testing", label: "Testing",
items: ["Pytest", "Selenium", "Playwright", "Jest", "API", "E2E", "Regression", "Contract"], items: ["Pytest", "Playwright", "Selenium", "Jest", "API", "E2E", "Regression", "Contract"],
}, },
{ {
label: "Observability", label: "Observability",
@ -339,40 +330,7 @@ const focusProfiles = {
}, },
}; };
export const homepageModes = [ export const homepageFocus = focusProfiles[focusMode] ? focusMode : "devops";
{ id: "sdet", label: "SDET", path: "/" },
{ id: "devops", label: "DevOps", path: "/devops" },
{ id: "developer", label: "Developer", path: "/developer" },
];
const focusAliases = {
dev: "developer",
development: "developer",
qa: "sdet",
test: "sdet",
testing: "sdet",
};
export function normalizeHomepageFocus(mode = "") {
const normalized = String(mode || "").trim().toLowerCase();
const aliased = focusAliases[normalized] || normalized;
return focusProfiles[aliased] ? aliased : "sdet";
}
export const homepageFocus = normalizeHomepageFocus(focusMode);
export function homepagePathForMode(mode = homepageFocus) {
return homepageModes.find((item) => item.id === normalizeHomepageFocus(mode))?.path || "/";
}
export function homepageAboutPathForMode(mode = homepageFocus) {
const focus = normalizeHomepageFocus(mode);
return focus === "sdet" ? "/about" : `/${focus}/about`;
}
export function homepageModeFromRoute(route) {
return normalizeHomepageFocus(route?.params?.focus || route?.meta?.homepageFocus || route?.query?.focus || homepageFocus);
}
const capabilities = [ const capabilities = [
{ {
@ -463,6 +421,8 @@ function orderedBy(items, order) {
return [...items].sort((a, b) => (rank.get(a.title) ?? 99) - (rank.get(b.title) ?? 99)); return [...items].sort((a, b) => (rank.get(a.title) ?? 99) - (rank.get(b.title) ?? 99));
} }
const selectedFocus = focusProfiles[homepageFocus];
const workDetailsByFocus = { const workDetailsByFocus = {
devops: { devops: {
titan: { titan: {
@ -523,7 +483,7 @@ const workDetailsByFocus = {
points: ["Python", "Selenium", "system E2E", "microservices", "web UI", "quality gates", "test architecture"], points: ["Python", "Selenium", "system E2E", "microservices", "web UI", "quality gates", "test architecture"],
}, },
}, },
developer: { dev: {
titan: { titan: {
kind: "Live platform", kind: "Live platform",
description: description:
@ -549,16 +509,13 @@ const workDetailsByFocus = {
}, },
}; };
export function homepageContentForMode(mode = homepageFocus) { const workDetails = workDetailsByFocus[homepageFocus] || workDetailsByFocus.devops;
const focus = normalizeHomepageFocus(mode);
const selectedFocus = focusProfiles[focus];
const workDetails = workDetailsByFocus[focus] || workDetailsByFocus.devops;
return { export const homepageContent = {
professionalName: "Brad Stein", professionalName: "Brad Stein",
role: selectedFocus.role, role: selectedFocus.role,
shortRole: selectedFocus.shortRole, shortRole: selectedFocus.shortRole,
focus, focus: homepageFocus,
eyebrow: selectedFocus.eyebrow, eyebrow: selectedFocus.eyebrow,
title: "Brad Stein | SDET, DevOps Automation & Platform Engineering", title: "Brad Stein | SDET, DevOps Automation & Platform Engineering",
description: description:
@ -575,7 +532,7 @@ export function homepageContentForMode(mode = homepageFocus) {
email: "brad@bstein.dev", email: "brad@bstein.dev",
subject: "Project, contract, or engineering inquiry from bstein.dev", subject: "Project, contract, or engineering inquiry from bstein.dev",
}, },
resumeUrl: homepageAboutPathForMode(focus), resumeUrl: "/about",
resumePdfUrl: "", resumePdfUrl: "",
linkedInUrl: "https://www.linkedin.com/in/steinbradley/", linkedInUrl: "https://www.linkedin.com/in/steinbradley/",
sourceUrl: "https://scm.bstein.dev/bstein", sourceUrl: "https://scm.bstein.dev/bstein",
@ -640,18 +597,18 @@ export function homepageContentForMode(mode = homepageFocus) {
{ {
icon: "AI", icon: "AI",
kind: "Community AI service", kind: "Community AI service",
title: "Cassandra", title: "Veles",
description: selectedFocus.cassandra.description, description: selectedFocus.veles.description,
points: selectedFocus.cassandra.points, points: selectedFocus.veles.points,
weight: "minor", weight: "minor",
links: [ links: [
{ {
label: "Live app", label: "Live app",
href: "https://cassandra.bstein.dev/", href: "https://veles.bstein.dev/",
}, },
{ {
label: "Content seed", label: "Content seed",
href: "https://scm.bstein.dev/bstein/cassandra-content", href: "https://scm.bstein.dev/bstein/veles-content",
}, },
], ],
}, },
@ -682,10 +639,8 @@ export function homepageContentForMode(mode = homepageFocus) {
title: "Senior Remote Engineering Role", title: "Senior Remote Engineering Role",
duration: "Full-time remote roles where the fit is strong.", duration: "Full-time remote roles where the fit is strong.",
description: description:
focus === "devops" homepageFocus === "devops"
? "I am open to well-aligned remote DevOps Automation, platform engineering, CI/CD, infrastructure automation, Kubernetes, Linux, or systems reliability roles. SDET and test automation depth comes with it." ? "I am open to well-aligned remote DevOps Automation, platform engineering, CI/CD, infrastructure automation, Kubernetes, Linux, or systems reliability roles. SDET and test automation depth comes with it."
: focus === "developer"
? "I am open to well-aligned remote developer, full-stack, platform product, AI service, Python, Vue, automation, or internal tooling roles. SDET and DevOps depth comes with it."
: "I am open to well-aligned remote SDET, QA Automation, systems testing, release quality, CI/CD, or DevOps Automation roles. Platform engineering depth comes with it.", : "I am open to well-aligned remote SDET, QA Automation, systems testing, release quality, CI/CD, or DevOps Automation roles. Platform engineering depth comes with it.",
}, },
].sort((a, b) => { ].sort((a, b) => {
@ -827,9 +782,9 @@ export function homepageContentForMode(mode = homepageFocus) {
description: "Ask the Titan Lab AI assistant for help.", description: "Ask the Titan Lab AI assistant for help.",
}, },
{ {
name: "Cassandra", name: "Veles",
icon: "🃏", icon: "🃏",
href: "https://cassandra.bstein.dev/", href: "https://veles.bstein.dev/",
description: "Test Magic: The Gathering deck ideas with AI playtesting.", description: "Test Magic: The Gathering deck ideas with AI playtesting.",
}, },
{ {
@ -839,13 +794,10 @@ export function homepageContentForMode(mode = homepageFocus) {
description: "Use the private RPC endpoint through the Monero guide.", description: "Use the private RPC endpoint through the Monero guide.",
}, },
], ],
}; };
}
export const homepageContent = homepageContentForMode(homepageFocus); export function contactMailto() {
const email = homepageContent.primaryContact.email;
export function contactMailto(content = homepageContent) { const subject = encodeURIComponent(homepageContent.primaryContact.subject);
const email = content.primaryContact.email;
const subject = encodeURIComponent(content.primaryContact.subject);
return `mailto:${email}?subject=${subject}`; return `mailto:${email}?subject=${subject}`;
} }

View File

@ -234,12 +234,12 @@ export function fallbackServices() {
status: "live", status: "live",
}, },
{ {
name: "Cassandra", name: "Veles",
icon: "🃏", icon: "🃏",
category: "ai", category: "ai",
summary: "Magic: The Gathering AI playtester for deck construction.", summary: "Magic: The Gathering AI playtester for deck construction.",
link: "https://cassandra.bstein.dev/", link: "https://veles.bstein.dev/",
host: "cassandra.bstein.dev", host: "veles.bstein.dev",
status: "live", status: "live",
}, },
{ {

View File

@ -12,11 +12,8 @@ import OnboardingView from "./views/OnboardingView.vue";
export default createRouter({ export default createRouter({
history: createWebHistory(), history: createWebHistory(),
routes: [ routes: [
{ path: "/", name: "home", component: HomeView, meta: { homepageFocus: "sdet" } }, { path: "/", name: "home", component: HomeView },
{ path: "/about", name: "about", component: AboutView, meta: { homepageFocus: "sdet" } }, { path: "/about", name: "about", component: AboutView },
{ path: "/dev", redirect: "/developer" },
{ path: "/:focus(devops|developer|sdet)", name: "home-focus", component: HomeView },
{ path: "/:focus(devops|developer|sdet)/about", name: "about-focus", component: AboutView },
{ path: "/ai", redirect: "/ai/chat" }, { path: "/ai", redirect: "/ai/chat" },
{ path: "/ai/chat", name: "ai-chat", component: AiView }, { path: "/ai/chat", name: "ai-chat", component: AiView },
{ path: "/ai/roadmap", name: "ai-roadmap", component: AiPlanView }, { path: "/ai/roadmap", name: "ai-roadmap", component: AiPlanView },

View File

@ -63,6 +63,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="divider"></div>
<p class="note">
Web version aligned to <span class="mono">Stein.Brad.Resume.2026.06.pdf</span>. Phone and street address are
intentionally omitted from the public site.
</p>
</section> </section>
<section class="card"> <section class="card">
@ -83,11 +88,10 @@
and operator workflows instead of leaving them as manual notes. and operator workflows instead of leaving them as manual notes.
</p> </p>
<p> <p>
Local hosting gives me room to build and operate in-development AI-powered systems on the same platform. <span class="mono">Oceanus</span> and <span class="mono">Tethys</span> are now part of Atlas capacity instead of separate public status surfaces.
<span class="mono">Cassandra</span>, an AI-powered Magic: The Gathering playtester that uses AI as a decision The dedicated non-cluster hosts are the control-plane database node and the jumphost. <span class="mono">Veles</span>,
engine, runs at the MTG AI playtester, is deployed at
<a href="https://cassandra.bstein.dev/" target="_blank" rel="noopener noreferrer">cassandra.bstein.dev</a> <a href="https://veles.bstein.dev/" target="_blank" rel="noopener noreferrer">veles.bstein.dev</a>.
and takes advantage of the multi-architecture Atlas cluster and its supporting systems.
</p> </p>
</div> </div>
</section> </section>
@ -99,9 +103,11 @@
</div> </div>
<div class="copy"> <div class="copy">
<p> <p>
Mechanical Engineering and Applied Math at the University of Texas at Austin, including Java, C++, probability, Studied Mechanical Engineering and Applied Math at the University of Texas at Austin, including Java, C++,
statistics, complex analysis, numerical methods, number theory, and differential equations. Emerging Scholars probability, statistics, complex analysis, numerical methods, number theory, and differential equations.
honors calculus student and later served as a teaching assistant for it. </p>
<p>
Participated in the Emerging Scholars honors calculus program and later served as an assistant for it.
</p> </p>
</div> </div>
</section> </section>
@ -109,13 +115,9 @@
</template> </template>
<script setup> <script setup>
import { computed } from "vue"; import { homepageContent } from "../data/homepageContent";
import { useRoute } from "vue-router";
import { homepageContentForMode, homepageModeFromRoute } from "../data/homepageContent";
const route = useRoute(); const about = homepageContent.about;
const content = computed(() => homepageContentForMode(homepageModeFromRoute(route)));
const about = computed(() => content.value.about);
const timeline = [ const timeline = [
{ {

View File

@ -85,10 +85,11 @@ const apiDisplay = apiUrl.host + apiUrl.pathname;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const profiles = [ const profiles = [
{ id: "atlas-quick", label: "Atlas Quick" },
{ id: "atlas-smart", label: "Atlas Smart" }, { id: "atlas-smart", label: "Atlas Smart" },
{ id: "atlas-genius", label: "Atlas Genius" }, { id: "atlas-genius", label: "Atlas Genius" },
]; ];
const activeProfile = ref("atlas-smart"); const activeProfile = ref("atlas-quick");
const profileState = reactive( const profileState = reactive(
Object.fromEntries( Object.fromEntries(
profiles.map((profile) => [ profiles.map((profile) => [
@ -243,8 +244,7 @@ function handleKeydown(e) {
async function copyCurl() { async function copyCurl() {
const target = current.value.meta.endpoint || apiUrl.toString(); const target = current.value.meta.endpoint || apiUrl.toString();
const body = JSON.stringify({ message: "hi", profile: activeProfile.value }); const curl = `curl -X POST ${target} -H 'content-type: application/json' -d '{\"message\":\"hi\"}'`;
const curl = `curl -X POST ${target} -H 'content-type: application/json' -d '${body}'`;
try { try {
await navigator.clipboard.writeText(curl); await navigator.clipboard.writeText(curl);
copied.value = true; copied.value = true;

File diff suppressed because it is too large Load Diff

View File

@ -49,7 +49,7 @@ test.beforeEach(async ({ page }) => {
body: JSON.stringify({ enabled: true, reset_url: "https://sso.example.dev/reset" }), body: JSON.stringify({ enabled: true, reset_url: "https://sso.example.dev/reset" }),
}); });
}); });
await page.route("**/api/lab/status*", async (route) => { await page.route("**/api/lab/status", async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: "application/json", contentType: "application/json",
@ -77,18 +77,15 @@ test("shows Brad's professional homepage and opens the platform diagram overlay"
await page.goto("/", { waitUntil: "domcontentloaded" }); await page.goto("/", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Brad Stein" })).toBeVisible(); await expect(page.getByRole("heading", { name: "Brad Stein" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Senior SDET / DevOps Automation Engineer" })).toBeVisible(); await expect(page.getByRole("heading", { name: "DevOps Automation Engineer / Senior SDET" })).toBeVisible();
await expect(page.getByRole("link", { name: "Discuss a Project" })).toBeVisible(); await expect(page.getByRole("link", { name: "Discuss a Project" })).toBeVisible();
await expect(page.getByRole("link", { name: "View Résumé" })).toBeVisible(); await expect(page.getByRole("link", { name: "View Résumé" })).toBeVisible();
await expect(page.getByRole("link", { name: "Explore the Live Platform" })).toBeVisible(); await expect(page.getByRole("link", { name: "Explore the Live Platform" })).toBeVisible();
await expect(page.getByRole("button", { name: "Login" })).toBeVisible(); await expect(page.getByRole("button", { name: "Login" })).toBeVisible();
await expect( await expect(page.getByRole("link", { name: "Register" })).toBeVisible();
page.getByLabel("Primary navigation").getByRole("link", { name: "Register" }), await expect(page.getByRole("heading", { name: "From 0 to 100" })).toBeVisible();
).toBeVisible();
await expect(page.getByRole("heading", { name: "Quality Systems I Can Take From Zero to Reliable" })).toBeVisible();
await expect(page.getByRole("link", { name: "Nextcloud", exact: true })).toBeVisible(); await expect(page.getByRole("link", { name: "Nextcloud", exact: true })).toBeVisible();
await expect(page.getByText("2 of 2 online")).toBeVisible(); await expect(page.getByText("2 of 2 responding")).toBeVisible();
await expect(page.getByText("Database machine")).toBeVisible();
await page.getByRole("link", { name: "Explore the Live Platform" }).click(); await page.getByRole("link", { name: "Explore the Live Platform" }).click();
await expect(page.getByRole("heading", { name: "Titan Lab: Live Platform" })).toBeVisible(); await expect(page.getByRole("heading", { name: "Titan Lab: Live Platform" })).toBeVisible();
@ -109,7 +106,7 @@ test("keeps the homepage usable when public status fails", async ({ page }) => {
await page.addInitScript(() => { await page.addInitScript(() => {
window.__LAB_STATUS_FAIL__ = true; window.__LAB_STATUS_FAIL__ = true;
}); });
await page.route("**/api/lab/status*", async (route) => { await page.route("**/api/lab/status", async (route) => {
await route.fulfill({ await route.fulfill({
status: 503, status: 503,
contentType: "application/json", contentType: "application/json",
@ -123,21 +120,3 @@ test("keeps the homepage usable when public status fails", async ({ page }) => {
await expect(page.getByText("Live status is temporarily unavailable").first()).toBeVisible(); await expect(page.getByText("Live status is temporarily unavailable").first()).toBeVisible();
await expect(page.getByText("status 503")).toHaveCount(0); await expect(page.getByText("status 503")).toHaveCount(0);
}); });
test("serves developer and SDET homepage modes", async ({ page }) => {
await page.goto("/developer", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Full-Stack Platform Engineer / Senior SDET" })).toBeVisible();
await expect(page.getByText("Product and Platform Showcase")).toBeVisible();
await expect(page.getByRole("link", { name: "Developer", exact: true })).toHaveClass(/active/);
await page.getByRole("link", { name: "View Résumé" }).click();
await expect(page).toHaveURL(/\/developer\/about$/);
await expect(page.getByText("AI Services · Vue · Python · Kubernetes")).toBeVisible();
await page.goto("/sdet", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Senior SDET / DevOps Automation Engineer" })).toBeVisible();
await expect(page.getByText("Testing, Automation, and Platform Work")).toBeVisible();
await expect(page.getByRole("link", { name: "SDET", exact: true })).toHaveClass(/active/);
await page.getByRole("link", { name: "View Résumé" }).click();
await expect(page).toHaveURL(/\/about$/);
await expect(page.getByText("Test Automation · Release Quality · CI/CD")).toBeVisible();
});

View File

@ -8,7 +8,7 @@ test.beforeEach(async ({ page }) => {
body: JSON.stringify({ enabled: false }), body: JSON.stringify({ enabled: false }),
}); });
}); });
await page.route("**/api/lab/status*", async (route) => { await page.route("**/api/lab/status", async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: "application/json", contentType: "application/json",

View File

@ -6,7 +6,6 @@ const testingDir = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({ export default defineConfig({
testDir: path.resolve(testingDir, "component"), testDir: path.resolve(testingDir, "component"),
retries: process.env.CI ? 1 : 0,
use: { use: {
ctPort: 3100, ctPort: 3100,
ctTemplateDir: "../../frontend/playwright", ctTemplateDir: "../../frontend/playwright",

View File

@ -9,7 +9,6 @@ const webServerCommand = process.env.PLAYWRIGHT_REUSE_DIST === "1" ? previewComm
export default defineConfig({ export default defineConfig({
testDir: path.resolve(testingDir, "e2e"), testDir: path.resolve(testingDir, "e2e"),
retries: process.env.CI ? 1 : 0,
workers: 1, workers: 1,
timeout: 60000, timeout: 60000,
expect: { expect: {

View File

@ -215,18 +215,7 @@ describe("account dashboard", () => {
return jsonResponse({ return jsonResponse({
can_control_gpu: true, can_control_gpu: true,
moonlight: { host: "moonlight.bstein.dev", source_ip: "181.1.87.186", unlock_ttl_seconds: 28800 }, moonlight: { host: "moonlight.bstein.dev", source_ip: "181.1.87.186", unlock_ttl_seconds: 28800 },
gpu: { gpu: { priority: "ai", game_mode: { status: "idle", active: false } },
priority: "ai",
game_mode: {
status: "idle",
active: false,
gpu_owner: "hermes",
model: "gpt-oss:20b",
model_loaded: true,
local_inference_ready: true,
inference_path: "local",
},
},
wolf: { wolf: {
api_enabled: true, api_enabled: true,
clients: [{ name: "Desktop" }], clients: [{ name: "Desktop" }],
@ -247,18 +236,13 @@ describe("account dashboard", () => {
expect(dashboard.wolf.status).toBe("ready"); expect(dashboard.wolf.status).toBe("ready");
expect(dashboard.wolf.canControlGpu).toBe(true); expect(dashboard.wolf.canControlGpu).toBe(true);
expect(dashboard.wolf.gpuOwner).toBe("hermes");
expect(dashboard.wolf.localModel).toBe("gpt-oss:20b");
expect(dashboard.wolf.localModelLoaded).toBe(true);
expect(dashboard.wolf.localInferenceReady).toBe(true);
expect(dashboard.wolf.inferencePath).toBe("local");
expect(dashboard.wolf.clients[0].name).toBe("Desktop"); expect(dashboard.wolf.clients[0].name).toBe("Desktop");
expect(dashboard.wolf.pendingPairRequests[0].pair_secret).toBe("secret-1"); expect(dashboard.wolf.pendingPairRequests[0].pair_secret).toBe("secret-1");
await dashboard.unlockWolf(); await dashboard.unlockWolf();
dashboard.wolf.pinInputs["secret-1"] = "1234"; dashboard.wolf.pinInputs["secret-1"] = "1234";
await dashboard.pairWolf("secret-1"); await dashboard.pairWolf("secret-1");
dashboard.wolf.selectedGame = "desktop"; dashboard.wolf.selectedGame = "arc-raiders";
dashboard.wolf.note = "now"; dashboard.wolf.note = "now";
await dashboard.setWolfGameMode("start"); await dashboard.setWolfGameMode("start");
await dashboard.setWolfGameMode("stop"); await dashboard.setWolfGameMode("stop");
@ -276,7 +260,7 @@ describe("account dashboard", () => {
source_ip: "181.1.87.186", source_ip: "181.1.87.186",
}); });
expect(JSON.parse(seen.find((item) => item.url.includes("/game-mode/start")).body)).toEqual({ expect(JSON.parse(seen.find((item) => item.url.includes("/game-mode/start")).body)).toEqual({
game: "desktop", game: "arc-raiders",
note: "now", note: "now",
}); });
expect(JSON.parse(seen.find((item) => item.url.includes("/admin/firewall/unlock")).body)).toEqual({ expect(JSON.parse(seen.find((item) => item.url.includes("/admin/firewall/unlock")).body)).toEqual({

View File

@ -55,7 +55,7 @@ describe("AI chat view", () => {
installFetch((url) => { installFetch((url) => {
if (url.includes("/api/ai/info")) { if (url.includes("/api/ai/info")) {
return jsonResponse({ return jsonResponse({
model: url.includes("atlas-genius") ? "genius-model" : "smart-model", model: url.includes("atlas-smart") ? "smart-model" : "quick-model",
gpu: "titan-24", gpu: "titan-24",
node: "titan-24", node: "titan-24",
endpoint: "https://ai.example.dev/chat", endpoint: "https://ai.example.dev/chat",
@ -76,18 +76,16 @@ describe("AI chat view", () => {
const wrapper = mount(AiView); const wrapper = mount(AiView);
await flushPromises(); await flushPromises();
expect(wrapper.text()).not.toContain("Atlas Quick"); expect(wrapper.text()).toContain("quick-model");
expect(wrapper.text()).toContain("smart-model");
expect(wrapper.text()).toContain("titan-24"); expect(wrapper.text()).toContain("titan-24");
await wrapper.find(".endpoint-copy").trigger("click"); await wrapper.find(".endpoint-copy").trigger("click");
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(expect.stringContaining("curl -X POST https://ai.example.dev/chat")); expect(navigator.clipboard.writeText).toHaveBeenCalledWith(expect.stringContaining("curl -X POST https://ai.example.dev/chat"));
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(expect.stringContaining('"profile":"atlas-smart"'));
expect(wrapper.text()).toContain("copied"); expect(wrapper.text()).toContain("copied");
await wrapper.findAll(".profile-tab").find((button) => button.text() === "Atlas Genius").trigger("click"); await wrapper.findAll(".profile-tab").find((button) => button.text() === "Atlas Smart").trigger("click");
await flushPromises(); await flushPromises();
expect(wrapper.text()).toContain("genius-model"); expect(wrapper.text()).toContain("smart-model");
Object.defineProperty(navigator, "clipboard", { Object.defineProperty(navigator, "clipboard", {
configurable: true, configurable: true,
@ -102,7 +100,7 @@ describe("AI chat view", () => {
it("sends JSON chat requests and reveals typed responses", async () => { it("sends JSON chat requests and reveals typed responses", async () => {
const bodies = []; const bodies = [];
installFetch((url, options) => { installFetch((url, options) => {
if (url.includes("/api/ai/info")) return jsonResponse({ model: "smart-model" }); if (url.includes("/api/ai/info")) return jsonResponse({ model: "quick-model" });
bodies.push(JSON.parse(options.body)); bodies.push(JSON.parse(options.body));
return jsonResponse({ reply: "typed assistant response", latency_ms: 42 }); return jsonResponse({ reply: "typed assistant response", latency_ms: 42 });
}); });
@ -119,10 +117,10 @@ describe("AI chat view", () => {
expect(wrapper.text()).toContain("42 ms"); expect(wrapper.text()).toContain("42 ms");
expect(bodies[0]).toMatchObject({ expect(bodies[0]).toMatchObject({
message: "hello atlas", message: "hello atlas",
profile: "atlas-smart", profile: "atlas-quick",
conversation_id: expect.stringContaining("atlas-smart"), conversation_id: expect.stringContaining("atlas-quick"),
}); });
expect(localStorage.getItem("atlas-ai-conversation:atlas-smart")).toContain("uuid-1"); expect(localStorage.getItem("atlas-ai-conversation:atlas-quick")).toContain("uuid-1");
await wrapper.find("textarea").setValue("second"); await wrapper.find("textarea").setValue("second");
await wrapper.find("form").trigger("submit.prevent"); await wrapper.find("form").trigger("submit.prevent");

View File

@ -53,9 +53,6 @@ describe("frontend entrypoint and router", () => {
expect(router.routes.map((route) => route.path)).toEqual([ expect(router.routes.map((route) => route.path)).toEqual([
"/", "/",
"/about", "/about",
"/dev",
"/:focus(devops|developer|sdet)",
"/:focus(devops|developer|sdet)/about",
"/ai", "/ai",
"/ai/chat", "/ai/chat",
"/ai/roadmap", "/ai/roadmap",
@ -66,9 +63,6 @@ describe("frontend entrypoint and router", () => {
"/onboarding", "/onboarding",
]); ]);
expect(router.routes.find((route) => route.path === "/ai")).toMatchObject({ redirect: "/ai/chat" }); expect(router.routes.find((route) => route.path === "/ai")).toMatchObject({ redirect: "/ai/chat" });
expect(router.routes.find((route) => route.path === "/dev")).toMatchObject({ redirect: "/developer" });
expect(router.routes.find((route) => route.name === "home-focus").component).toBeDefined();
expect(router.routes.find((route) => route.name === "about-focus").component).toBeDefined();
expect(router.routes.find((route) => route.name === "account").component).toBeDefined(); expect(router.routes.find((route) => route.name === "account").component).toBeDefined();
}); });
}); });

View File

@ -1,22 +1,10 @@
import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals"; import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { RouterLinkStub, flushPromises, mount, shallowMount } from "@vue/test-utils"; import { RouterLinkStub, flushPromises, shallowMount } from "@vue/test-utils";
import PlatformSection from "../../../frontend/src/components/PlatformSection.vue"; import PlatformSection from "../../../frontend/src/components/PlatformSection.vue";
import { auth } from "../../../frontend/src/auth.js"; import { auth } from "../../../frontend/src/auth.js";
import { homepageContentForMode, normalizeHomepageFocus } from "../../../frontend/src/data/homepageContent.js";
import HomeView from "../../../frontend/src/views/HomeView.vue"; import HomeView from "../../../frontend/src/views/HomeView.vue";
let mockRoute = { meta: { homepageFocus: "sdet" }, params: {}, query: {} };
jest.mock("vue-router", () => ({
RouterLink: {
name: "RouterLink",
props: ["to"],
template: "<a :href=\"typeof to === 'string' ? to : '#'\" @click=\"$emit('click', $event)\"><slot /></a>",
},
useRoute: () => mockRoute,
}), { virtual: true });
function resetAuth() { function resetAuth() {
auth.ready = true; auth.ready = true;
auth.enabled = true; auth.enabled = true;
@ -30,7 +18,6 @@ function resetAuth() {
describe("HomeView", () => { describe("HomeView", () => {
beforeEach(() => { beforeEach(() => {
mockRoute = { meta: { homepageFocus: "sdet" }, params: {}, query: {} };
global.fetch = jest.fn(async (resource) => { global.fetch = jest.fn(async (resource) => {
const url = typeof resource === "string" ? resource : resource?.url || ""; const url = typeof resource === "string" ? resource : resource?.url || "";
if (url.includes("/api/account/member-state")) { if (url.includes("/api/account/member-state")) {
@ -55,7 +42,7 @@ describe("HomeView", () => {
it("renders the professional homepage while logged out", () => { it("renders the professional homepage while logged out", () => {
resetAuth(); resetAuth();
const wrapper = mount(HomeView, { const wrapper = shallowMount(HomeView, {
global: { global: {
stubs: { stubs: {
RouterLink: RouterLinkStub, RouterLink: RouterLinkStub,
@ -85,14 +72,14 @@ describe("HomeView", () => {
}); });
expect(wrapper.text()).toContain("Brad Stein"); expect(wrapper.text()).toContain("Brad Stein");
expect(wrapper.text()).toContain("Senior SDET / DevOps Automation Engineer"); expect(wrapper.text()).toContain("DevOps Automation Engineer / Senior SDET");
expect(wrapper.text()).toContain("Discuss a Project"); expect(wrapper.text()).toContain("Discuss a Project");
expect(wrapper.text()).toContain("View Résumé"); expect(wrapper.text()).toContain("View Résumé");
expect(wrapper.text()).toContain("Explore the Live Platform"); expect(wrapper.text()).toContain("Explore the Live Platform");
expect(wrapper.text()).toContain("Quality Systems I Can Take From Zero to Reliable"); expect(wrapper.text()).toContain("From 0 to 100");
expect(wrapper.text()).toContain("Testing, Automation, and Platform Work"); expect(wrapper.text()).toContain("Platform Showcase");
expect(wrapper.text()).toContain("Atlas operator tools"); expect(wrapper.text()).toContain("Atlas operator tools");
expect(wrapper.text()).toContain("Cassandra"); expect(wrapper.text()).toContain("Veles");
expect(wrapper.text()).not.toContain("TaskWatcher"); expect(wrapper.text()).not.toContain("TaskWatcher");
expect(wrapper.text()).toContain("family, friends, and the lucky few"); expect(wrapper.text()).toContain("family, friends, and the lucky few");
expect(wrapper.text()).toContain("Register"); expect(wrapper.text()).toContain("Register");
@ -109,19 +96,17 @@ describe("HomeView", () => {
expect(wrapper.text()).not.toContain("Looking for hosted services or an existing Titan Lab account"); expect(wrapper.text()).not.toContain("Looking for hosted services or an existing Titan Lab account");
expect(wrapper.find("a[href^='mailto:brad@bstein.dev']").exists()).toBe(true); expect(wrapper.find("a[href^='mailto:brad@bstein.dev']").exists()).toBe(true);
expect(wrapper.findComponent(PlatformSection).exists()).toBe(true); expect(wrapper.findComponent(PlatformSection).exists()).toBe(true);
expect(wrapper.text()).toContain("Atlas cluster"); expect(wrapper.text()).toContain("Atlas platform");
expect(wrapper.text()).toContain("Most requested service"); expect(wrapper.text()).toContain("Most active service");
expect(wrapper.text()).toContain("Nextcloud"); expect(wrapper.text()).toContain("Nextcloud");
expect(wrapper.text()).toContain("Outpost hosts"); expect(wrapper.text()).toContain("Dedicated hosts");
expect(wrapper.text()).toContain("2 of 2 online"); expect(wrapper.text()).toContain("2 of 2 responding");
expect(wrapper.text()).toContain("Database machine");
expect(wrapper.text()).toContain("Jumphost");
expect(wrapper.text()).toContain("Responding"); expect(wrapper.text()).toContain("Responding");
}); });
it("shows a calm fallback when public status is unavailable", () => { it("shows a calm fallback when public status is unavailable", () => {
resetAuth(); resetAuth();
const wrapper = mount(HomeView, { const wrapper = shallowMount(HomeView, {
global: { global: {
stubs: { stubs: {
RouterLink: RouterLinkStub, RouterLink: RouterLinkStub,
@ -145,7 +130,7 @@ describe("HomeView", () => {
auth.username = "ada"; auth.username = "ada";
auth.email = "ada@example.dev"; auth.email = "ada@example.dev";
const wrapper = mount(HomeView, { const wrapper = shallowMount(HomeView, {
global: { global: {
stubs: { stubs: {
RouterLink: RouterLinkStub, RouterLink: RouterLinkStub,
@ -165,50 +150,6 @@ describe("HomeView", () => {
expect(wrapper.text()).not.toContain("app_password"); expect(wrapper.text()).not.toContain("app_password");
expect(wrapper.text()).not.toContain("mailu_app_password"); expect(wrapper.text()).not.toContain("mailu_app_password");
}); });
it("builds developer and SDET homepage modes", () => {
const devops = homepageContentForMode("devops");
const developer = homepageContentForMode("developer");
const sdet = homepageContentForMode("sdet");
expect(normalizeHomepageFocus("dev")).toBe("developer");
expect(devops.summary).toContain("custom software, tooling, and infrastructure");
expect(devops.techStack).toEqual([
"Kubernetes",
"Docker",
"Linux",
"Terraform",
"Ansible",
"Flux",
"Helm",
"Jenkins",
"Harbor",
"Keycloak",
"Vault",
"Grafana",
"Prometheus",
"Python",
"Bash",
"Pytest",
"Selenium",
"Playwright",
]);
expect(developer.focus).toBe("developer");
expect(developer.role).toBe("Full-Stack Platform Engineer / Senior SDET");
expect(developer.resumeUrl).toBe("/developer/about");
expect(developer.techStack).toContain("Vue");
expect(developer.techStack.indexOf("Selenium")).toBeLessThan(developer.techStack.indexOf("Playwright"));
expect(developer.workSection.heading).toBe("Product and Platform Showcase");
expect(developer.selectedWork[0].title).toBe("Cassandra");
expect(sdet.focus).toBe("sdet");
expect(sdet.role).toBe("Senior SDET / DevOps Automation Engineer");
expect(sdet.resumeUrl).toBe("/about");
expect(sdet.techStack).toContain("Selenium");
expect(sdet.techStack.indexOf("Selenium")).toBeLessThan(sdet.techStack.indexOf("Playwright"));
expect(sdet.capabilities[0].title).toBe("Test Automation & SDET");
expect(sdet.selectedWork[0].title).toBe("Boeing");
});
}); });
describe("PlatformSection", () => { describe("PlatformSection", () => {

View File

@ -20,7 +20,7 @@ describe("sample data builders", () => {
expect(hardware.specialty.map((node) => node.alias)).toEqual(["atlas-db", "theia"]); expect(hardware.specialty.map((node) => node.alias)).toEqual(["atlas-db", "theia"]);
expect(services.services.some((service) => service.name === "Keycloak")).toBe(true); expect(services.services.some((service) => service.name === "Keycloak")).toBe(true);
expect(services.services.some((service) => service.name === "Oceanus")).toBe(false); expect(services.services.some((service) => service.name === "Oceanus")).toBe(false);
expect(services.services.some((service) => service.name === "Cassandra")).toBe(true); expect(services.services.some((service) => service.name === "Veles")).toBe(true);
expect(services.services.some((service) => service.name === "AI Chat")).toBe(true); expect(services.services.some((service) => service.name === "AI Chat")).toBe(true);
expect(services.services.find((service) => service.name === "AI Chat").link).toBe("https://bstein.dev/ai/chat"); expect(services.services.find((service) => service.name === "AI Chat").link).toBe("https://bstein.dev/ai/chat");
expect(services.services.find((service) => service.name === "Monero").link).toBe("https://bstein.dev/monero"); expect(services.services.find((service) => service.name === "Monero").link).toBe("https://bstein.dev/monero");

View File

@ -1,6 +1,5 @@
import { afterEach, describe, expect, it, jest } from "@jest/globals"; import { afterEach, describe, expect, it, jest } from "@jest/globals";
import { flushPromises, mount, shallowMount } from "@vue/test-utils"; import { flushPromises, mount, shallowMount } from "@vue/test-utils";
import { nextTick } from "vue";
import axios from "axios"; import axios from "axios";
@ -27,7 +26,6 @@ jest.mock("vue-router", () => ({
props: ["to"], props: ["to"],
template: "<a :href=\"typeof to === 'string' ? to : '#'\" @click=\"$emit('click', $event)\"><slot /></a>", template: "<a :href=\"typeof to === 'string' ? to : '#'\" @click=\"$emit('click', $event)\"><slot /></a>",
}, },
useRoute: () => ({ meta: { homepageFocus: "sdet" }, params: {}, query: {} }),
}), { virtual: true }); }), { virtual: true });
describe("static shell views and components", () => { describe("static shell views and components", () => {
@ -45,7 +43,7 @@ describe("static shell views and components", () => {
const about = shallowMount(AboutView); const about = shallowMount(AboutView);
expect(about.text()).toContain("About Me"); expect(about.text()).toContain("About Me");
expect(about.text()).toContain("Titan Lab"); expect(about.text()).toContain("Titan Lab");
expect(about.text()).toContain("Senior SDET / DevOps Automation Engineer"); expect(about.text()).toContain("DevOps Automation Engineer / Senior SDET");
expect(about.text()).toContain("Kubernetes"); expect(about.text()).toContain("Kubernetes");
expect(about.text()).toContain("IBM Cloud"); expect(about.text()).toContain("IBM Cloud");
expect(about.text()).toContain("TradeHat"); expect(about.text()).toContain("TradeHat");
@ -125,51 +123,17 @@ describe("static shell views and components", () => {
const logout = jest.spyOn(authModule, "logout").mockImplementation(() => {}); const logout = jest.spyOn(authModule, "logout").mockImplementation(() => {});
jest.spyOn(authModule, "login").mockImplementation(() => {}); jest.spyOn(authModule, "login").mockImplementation(() => {});
const target = document.createElement("div"); const wrapper = mount(TopBar);
document.body.appendChild(target);
const wrapper = mount(TopBar, { attachTo: target });
try {
expect(wrapper.text()).toContain("Contact"); expect(wrapper.text()).toContain("Contact");
expect(wrapper.text()).toContain("DevOps");
expect(wrapper.text()).toContain("Developer");
expect(wrapper.text()).toContain("SDET");
expect(wrapper.text()).toContain("Login"); expect(wrapper.text()).toContain("Login");
expect(wrapper.text()).toContain("Register"); expect(wrapper.text()).toContain("Register");
expect(wrapper.text()).not.toContain("Reset Password"); expect(wrapper.text()).not.toContain("Reset Password");
expect(wrapper.find("a[href='/#contact']").exists()).toBe(true); expect(wrapper.find("a[href='/#contact']").exists()).toBe(true);
expect(wrapper.find("a[href='/devops']").exists()).toBe(true);
expect(wrapper.find("a[href='/developer']").exists()).toBe(true);
expect(wrapper.find(".mode-link.active").text()).toBe("SDET");
await wrapper.find("button.menu-toggle").trigger("click");
const nav = wrapper.find("nav");
expect(nav.classes()).toContain("open");
const navItems = () => Array.from(nav.element.querySelectorAll("a[href], button:not([disabled])"));
navItems()[0].focus();
nav.element.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", shiftKey: true, bubbles: true, cancelable: true }));
await nextTick();
expect(document.activeElement).toBe(navItems().at(-1));
navItems().at(-1).focus();
nav.element.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true }));
await nextTick();
expect(document.activeElement).toBe(navItems()[0]);
nav.element.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
await nextTick();
expect(nav.classes()).not.toContain("open");
expect(document.activeElement).toBe(wrapper.find("button.menu-toggle").element);
await wrapper.find("button.menu-toggle").trigger("click");
expect(nav.classes()).toContain("open");
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
await nextTick();
expect(nav.classes()).not.toContain("open");
await wrapper.find("button.menu-toggle").trigger("click"); await wrapper.find("button.menu-toggle").trigger("click");
expect(wrapper.find("nav").classes()).toContain("open");
await wrapper.find("a[href='/#contact']").trigger("click"); await wrapper.find("a[href='/#contact']").trigger("click");
expect(nav.classes()).not.toContain("open"); expect(wrapper.find("nav").classes()).not.toContain("open");
auth.authenticated = true; auth.authenticated = true;
await flushPromises(); await flushPromises();
@ -177,10 +141,6 @@ describe("static shell views and components", () => {
expect(wrapper.text()).toContain("Open Services"); expect(wrapper.text()).toContain("Open Services");
await wrapper.findAll("button.button").at(-1).trigger("click"); await wrapper.findAll("button.button").at(-1).trigger("click");
expect(logout).toHaveBeenCalled(); expect(logout).toHaveBeenCalled();
} finally {
wrapper.unmount();
target.remove();
}
}); });
it("loads Monero status and handles API failures", async () => { it("loads Monero status and handles API failures", async () => {
@ -222,13 +182,6 @@ describe("static shell views and components", () => {
}); });
await flushPromises(); await flushPromises();
expect(app.text()).toContain("true"); expect(app.text()).toContain("true");
expect(global.fetch).toHaveBeenCalledWith(
expect.stringMatching(/^\/api\/lab\/status\?ts=\d+$/),
expect.objectContaining({
cache: "no-store",
headers: { Accept: "application/json" },
}),
);
await app.unmount(); await app.unmount();