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

View File

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

View File

@ -22,18 +22,18 @@ def register(app) -> None:
payload = request.get_json(silent=True) or {}
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 ""
if not user_message:
return jsonify({"error": "message required"}), 400
started = time.time()
mode = "smart"
if profile in {"atlas-genius", "genius"}:
mode = "genius"
elif profile in {"atlas-quick", "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)
source = f"atlas-{mode}"
if reply:
@ -67,7 +67,7 @@ def register(app) -> None:
def ai_info() -> Any:
"""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)
return jsonify(meta)

View File

@ -14,15 +14,6 @@ from .. import settings
_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:
"""Run one instant VictoriaMetrics query and return the largest value."""
@ -180,7 +171,7 @@ def register(app) -> None:
now = time.time()
cached = _LAB_STATUS_CACHE.get("value")
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()
timings_ms: dict[str, int] = {}
@ -251,4 +242,4 @@ def register(app) -> None:
_LAB_STATUS_CACHE["ts"] = now
_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_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_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:
def __init__(self, enabled: bool = True) -> None:
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:
return self._enabled
def proxy(
self,
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))
def proxy(self, method: str, path: str, payload: object | None = None, params: dict | None = None):
self.calls.append((method, path, payload, 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 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:
@ -61,7 +54,7 @@ def test_wolf_status_prefers_public_query_ip(monkeypatch) -> None:
)
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:
@ -75,7 +68,7 @@ def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None:
)
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:
@ -88,7 +81,7 @@ def test_wolf_unlock_prefers_public_payload_ip(monkeypatch) -> None:
)
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:
@ -101,7 +94,7 @@ def test_wolf_unlock_ignores_private_payload_ip(monkeypatch) -> None:
)
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:
@ -115,7 +108,7 @@ def test_wolf_source_ip_prefers_nearest_public_proxy_value(monkeypatch) -> None:
)
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:
@ -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"})
assert ariadne.calls == [
("GET", "/api/game-stream/pairing/status", None, {"source_ip": "181.1.87.186"}, None),
("POST", "/api/game-stream/pairing/submit-pin", {"pair_secret": "secret", "pin": "1234", "source_ip": "181.1.87.186"}, None, None),
("POST", "/api/admin/game-mode/start", {"game": "steam"}, None, account_wolf.settings.ARIADNE_GAME_MODE_TIMEOUT_SEC),
("POST", "/api/admin/game-mode/stop", {"game": "steam"}, None, account_wolf.settings.ARIADNE_GAME_MODE_TIMEOUT_SEC),
("POST", "/api/admin/game-stream/firewall/unlock", {"ip": "8.8.8.8", "target_user": "olya"}, None, 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),
("POST", "/api/admin/game-mode/start", {"game": "steam"}, None),
("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),
]
@ -150,7 +143,7 @@ def test_wolf_user_revoke_accepts_public_payload_ip(monkeypatch) -> None:
)
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:

View File

@ -45,28 +45,12 @@ class AiRouteTests(TestCase):
self.assertEqual(data.get("source"), f"atlas-{expected_mode}")
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(
seen,
[
("quick", "conv-atlas-quick"),
("smart", "conv-atlas-smart"),
("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("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):
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()
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["atlas"]["source"] == "grafana"
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")
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["value"] = None

View File

@ -32,7 +32,7 @@
{
"@type": "Person",
"name": "Brad Stein",
"jobTitle": "Senior SDET / DevOps Automation Engineer",
"jobTitle": "DevOps Automation Engineer / Senior SDET",
"url": "https://bstein.dev/",
"email": "mailto:brad@bstein.dev",
"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">
<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>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
@ -20,7 +20,7 @@
<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="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="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>

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

View File

@ -47,13 +47,8 @@ export function useWolfDashboard() {
sourceIp: "",
unlockTtlSeconds: 28800,
gpuPriority: "unknown",
gpuOwner: "unknown",
gameModeStatus: "unknown",
gameModeActive: false,
localModel: "unknown",
localModelLoaded: false,
localInferenceReady: false,
inferencePath: "fallback",
selectedGame: "steam",
note: "",
manualIp: "",
@ -87,14 +82,8 @@ export function useWolfDashboard() {
wolf.sourceIp = data.moonlight?.source_ip || "";
wolf.unlockTtlSeconds = Number(data.moonlight?.unlock_ttl_seconds || 28800);
wolf.gpuPriority = data.gpu?.priority || "unknown";
const gameMode = data.gpu?.game_mode || {};
wolf.gpuOwner = gameMode.gpu_owner || "unknown";
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.gameModeStatus = data.gpu?.game_mode?.status || "unknown";
wolf.gameModeActive = Boolean(data.gpu?.game_mode?.active);
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.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("grafana")) return "GF";
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 image") || h.includes("vision")) return "VI";
if (h.includes("ai speech")) return "SP";
@ -366,7 +366,7 @@ function serviceRank(service) {
"Flux",
"Grafana",
"OpenSearch",
"Cassandra",
"Veles",
"Nextcloud",
"Outline",
"Planka",

View File

@ -1,7 +1,7 @@
<template>
<header class="topbar">
<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">
<img src="@/assets/profile-avatar.jpg" alt="" />
</div>
@ -36,20 +36,7 @@
<a v-for="link in sectionLinks" :key="link.href" class="nav-link" :href="link.href" @click="closeMenu">
{{ link.label }}
</a>
<a class="nav-link professional-action" :href="`${homePath}#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>
<a class="nav-link professional-action" href="/#contact" @click="closeMenu">Contact</a>
<div class="utility">
<template v-if="!auth.ready">
@ -75,29 +62,21 @@
</template>
<script setup>
import { computed, nextTick, onUnmounted, ref, watch } from "vue";
import { RouterLink, useRoute } from "vue-router";
import { nextTick, onUnmounted, ref, watch } from "vue";
import { RouterLink } from "vue-router";
import { auth, login, logout } from "@/auth";
import {
homepageContentForMode,
homepageModeFromRoute,
homepageModes,
homepagePathForMode,
} from "@/data/homepageContent";
import { homepageContent } from "@/data/homepageContent";
import { useMemberAccessState } from "@/member/useMemberAccessState";
const { memberAction } = useMemberAccessState();
const route = useRoute();
const currentMode = computed(() => homepageModeFromRoute(route));
const content = computed(() => homepageContentForMode(currentMode.value));
const homePath = computed(() => homepagePathForMode(currentMode.value));
const content = homepageContent;
const sectionLinks = computed(() => [
{ label: "Work", href: `${homePath.value}#work` },
{ label: "Capabilities", href: `${homePath.value}#capabilities` },
{ label: "Platform", href: `${homePath.value}#platform` },
{ label: "Process", href: `${homePath.value}#process` },
]);
const sectionLinks = [
{ label: "Work", href: "/#work" },
{ label: "Capabilities", href: "/#capabilities" },
{ label: "Platform", href: "/#platform" },
{ label: "Process", href: "/#process" },
];
const menuOpen = ref(false);
const navEl = ref(null);
@ -124,10 +103,6 @@ function onWindowKeydown(event) {
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) {
if (event.key === "Escape") {
closeMenu();
@ -260,15 +235,6 @@ onUnmounted(() => {
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 {
min-height: 44px;
display: inline-flex;
@ -283,18 +249,6 @@ onUnmounted(() => {
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 {
background: transparent;
cursor: pointer;
@ -382,16 +336,6 @@ onUnmounted(() => {
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 {
justify-content: flex-start;
white-space: normal;

View File

@ -35,12 +35,6 @@
<span class="k mono">GPU</span>
<strong class="mono">{{ wolf.gpuPriority }}</strong>
</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">
<span class="k mono">Firewall</span>
<strong class="mono" :class="currentIpUnlocked ? 'ok-text' : 'warn-text'">
@ -66,14 +60,6 @@
<span class="k mono">Paired devices</span>
<span class="v mono">{{ pairedDeviceNames }}</span>
</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 class="actions">
@ -107,7 +93,7 @@
<div v-if="wolf.canControlGpu" class="secret-box">
<div class="secret-head">
<div class="pill mono">Admin</div>
<span class="hint mono">RTX 3080 checkout</span>
<span class="hint mono">GPU priority</span>
</div>
<div class="wolf-controls">
<select v-model="wolf.selectedGame" class="input mono">
@ -115,19 +101,15 @@
<option value="arc-raiders">Arc Raiders</option>
<option value="satisfactory">Satisfactory</option>
<option value="wolf">Wolf</option>
<option value="desktop">Desktop + Wolf</option>
</select>
<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')">
{{ wolf.actioning === "start" ? "Reserving..." : "Reserve RTX 3080" }}
{{ wolf.actioning === "start" ? "Starting..." : "Prioritize Wolf" }}
</button>
<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>
</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">
<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" />

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 = {
devops: {
@ -6,27 +6,21 @@ const focusProfiles = {
role: "DevOps Automation Engineer / Senior SDET",
shortRole: "DevOps Automation / Platform",
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:
"Engineering experience across Boeing, IBM, deployment automation, infrastructure tooling, system testing, and production-minded platform operations.",
techStack: [
"Kubernetes",
"Jenkins",
"Docker",
"Kubernetes",
"Linux",
"Terraform",
"Ansible",
"Flux",
"Helm",
"Jenkins",
"Harbor",
"Keycloak",
"Vault",
"Grafana",
"Prometheus",
"Python",
"Bash",
"Pytest",
"Selenium",
"Playwright",
],
capabilityOrder: [
@ -63,7 +57,7 @@ const focusProfiles = {
description:
"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: [
"Fixed-Scope Technical Rescue",
"Short-Term Remote Contract",
@ -75,7 +69,7 @@ const focusProfiles = {
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.",
},
cassandra: {
veles: {
description:
"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"],
@ -129,15 +123,14 @@ const focusProfiles = {
"Pytest",
"Selenium",
"Playwright",
"API Testing",
"E2E Testing",
"CI/CD",
"API testing",
"Jenkins",
"Docker",
"Kubernetes",
"Linux",
"Grafana",
"Prometheus",
"Terraform",
],
capabilityOrder: [
"Test Automation & SDET",
@ -173,7 +166,7 @@ const focusProfiles = {
description:
"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: [
"Short-Term Remote Contract",
"Fixed-Scope Technical Rescue",
@ -185,7 +178,7 @@ const focusProfiles = {
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.",
},
cassandra: {
veles: {
description:
"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"],
@ -226,7 +219,7 @@ const focusProfiles = {
],
},
},
developer: {
dev: {
eyebrow: "AVAILABLE FOR REMOTE PRODUCT, PLATFORM, AND AI SERVICE WORK",
role: "Full-Stack Platform Engineer / Senior SDET",
shortRole: "Product / Platform Engineering",
@ -237,17 +230,15 @@ const focusProfiles = {
techStack: [
"Vue",
"Python",
"Flask",
"API Design",
"PostgreSQL",
"Redis",
"Workers",
"AI Integration",
"AI integration",
"API design",
"Docker",
"Kubernetes",
"CI/CD",
"Jenkins",
"Flux",
"Grafana",
"PostgreSQL",
"Pytest",
"Selenium",
"Playwright",
],
capabilityOrder: [
@ -284,7 +275,7 @@ const focusProfiles = {
description:
"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: [
"Short-Term Remote Contract",
"Fixed-Scope Technical Rescue",
@ -296,7 +287,7 @@ const focusProfiles = {
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.",
},
cassandra: {
veles: {
description:
"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"],
@ -324,7 +315,7 @@ const focusProfiles = {
},
{
label: "Testing",
items: ["Pytest", "Selenium", "Playwright", "Jest", "API", "E2E", "Regression", "Contract"],
items: ["Pytest", "Playwright", "Selenium", "Jest", "API", "E2E", "Regression", "Contract"],
},
{
label: "Observability",
@ -339,40 +330,7 @@ const focusProfiles = {
},
};
export const homepageModes = [
{ 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);
}
export const homepageFocus = focusProfiles[focusMode] ? focusMode : "devops";
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));
}
const selectedFocus = focusProfiles[homepageFocus];
const workDetailsByFocus = {
devops: {
titan: {
@ -523,7 +483,7 @@ const workDetailsByFocus = {
points: ["Python", "Selenium", "system E2E", "microservices", "web UI", "quality gates", "test architecture"],
},
},
developer: {
dev: {
titan: {
kind: "Live platform",
description:
@ -549,16 +509,13 @@ const workDetailsByFocus = {
},
};
export function homepageContentForMode(mode = homepageFocus) {
const focus = normalizeHomepageFocus(mode);
const selectedFocus = focusProfiles[focus];
const workDetails = workDetailsByFocus[focus] || workDetailsByFocus.devops;
const workDetails = workDetailsByFocus[homepageFocus] || workDetailsByFocus.devops;
return {
export const homepageContent = {
professionalName: "Brad Stein",
role: selectedFocus.role,
shortRole: selectedFocus.shortRole,
focus,
focus: homepageFocus,
eyebrow: selectedFocus.eyebrow,
title: "Brad Stein | SDET, DevOps Automation & Platform Engineering",
description:
@ -575,7 +532,7 @@ export function homepageContentForMode(mode = homepageFocus) {
email: "brad@bstein.dev",
subject: "Project, contract, or engineering inquiry from bstein.dev",
},
resumeUrl: homepageAboutPathForMode(focus),
resumeUrl: "/about",
resumePdfUrl: "",
linkedInUrl: "https://www.linkedin.com/in/steinbradley/",
sourceUrl: "https://scm.bstein.dev/bstein",
@ -640,18 +597,18 @@ export function homepageContentForMode(mode = homepageFocus) {
{
icon: "AI",
kind: "Community AI service",
title: "Cassandra",
description: selectedFocus.cassandra.description,
points: selectedFocus.cassandra.points,
title: "Veles",
description: selectedFocus.veles.description,
points: selectedFocus.veles.points,
weight: "minor",
links: [
{
label: "Live app",
href: "https://cassandra.bstein.dev/",
href: "https://veles.bstein.dev/",
},
{
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",
duration: "Full-time remote roles where the fit is strong.",
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."
: 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.",
},
].sort((a, b) => {
@ -827,9 +782,9 @@ export function homepageContentForMode(mode = homepageFocus) {
description: "Ask the Titan Lab AI assistant for help.",
},
{
name: "Cassandra",
name: "Veles",
icon: "🃏",
href: "https://cassandra.bstein.dev/",
href: "https://veles.bstein.dev/",
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.",
},
],
};
}
};
export const homepageContent = homepageContentForMode(homepageFocus);
export function contactMailto(content = homepageContent) {
const email = content.primaryContact.email;
const subject = encodeURIComponent(content.primaryContact.subject);
export function contactMailto() {
const email = homepageContent.primaryContact.email;
const subject = encodeURIComponent(homepageContent.primaryContact.subject);
return `mailto:${email}?subject=${subject}`;
}

View File

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

View File

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

View File

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

View File

@ -85,10 +85,11 @@ const apiDisplay = apiUrl.host + apiUrl.pathname;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const profiles = [
{ id: "atlas-quick", label: "Atlas Quick" },
{ id: "atlas-smart", label: "Atlas Smart" },
{ id: "atlas-genius", label: "Atlas Genius" },
];
const activeProfile = ref("atlas-smart");
const activeProfile = ref("atlas-quick");
const profileState = reactive(
Object.fromEntries(
profiles.map((profile) => [
@ -243,8 +244,7 @@ function handleKeydown(e) {
async function copyCurl() {
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 '${body}'`;
const curl = `curl -X POST ${target} -H 'content-type: application/json' -d '{\"message\":\"hi\"}'`;
try {
await navigator.clipboard.writeText(curl);
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" }),
});
});
await page.route("**/api/lab/status*", async (route) => {
await page.route("**/api/lab/status", async (route) => {
await route.fulfill({
status: 200,
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 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: "View Résumé" })).toBeVisible();
await expect(page.getByRole("link", { name: "Explore the Live Platform" })).toBeVisible();
await expect(page.getByRole("button", { name: "Login" })).toBeVisible();
await expect(
page.getByLabel("Primary navigation").getByRole("link", { name: "Register" }),
).toBeVisible();
await expect(page.getByRole("heading", { name: "Quality Systems I Can Take From Zero to Reliable" })).toBeVisible();
await expect(page.getByRole("link", { name: "Register" })).toBeVisible();
await expect(page.getByRole("heading", { name: "From 0 to 100" })).toBeVisible();
await expect(page.getByRole("link", { name: "Nextcloud", exact: true })).toBeVisible();
await expect(page.getByText("2 of 2 online")).toBeVisible();
await expect(page.getByText("Database machine")).toBeVisible();
await expect(page.getByText("2 of 2 responding")).toBeVisible();
await page.getByRole("link", { name: "Explore the Live Platform" }).click();
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(() => {
window.__LAB_STATUS_FAIL__ = true;
});
await page.route("**/api/lab/status*", async (route) => {
await page.route("**/api/lab/status", async (route) => {
await route.fulfill({
status: 503,
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("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 }),
});
});
await page.route("**/api/lab/status*", async (route) => {
await page.route("**/api/lab/status", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",

View File

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

View File

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

View File

@ -215,18 +215,7 @@ describe("account dashboard", () => {
return jsonResponse({
can_control_gpu: true,
moonlight: { host: "moonlight.bstein.dev", source_ip: "181.1.87.186", unlock_ttl_seconds: 28800 },
gpu: {
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",
},
},
gpu: { priority: "ai", game_mode: { status: "idle", active: false } },
wolf: {
api_enabled: true,
clients: [{ name: "Desktop" }],
@ -247,18 +236,13 @@ describe("account dashboard", () => {
expect(dashboard.wolf.status).toBe("ready");
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.pendingPairRequests[0].pair_secret).toBe("secret-1");
await dashboard.unlockWolf();
dashboard.wolf.pinInputs["secret-1"] = "1234";
await dashboard.pairWolf("secret-1");
dashboard.wolf.selectedGame = "desktop";
dashboard.wolf.selectedGame = "arc-raiders";
dashboard.wolf.note = "now";
await dashboard.setWolfGameMode("start");
await dashboard.setWolfGameMode("stop");
@ -276,7 +260,7 @@ describe("account dashboard", () => {
source_ip: "181.1.87.186",
});
expect(JSON.parse(seen.find((item) => item.url.includes("/game-mode/start")).body)).toEqual({
game: "desktop",
game: "arc-raiders",
note: "now",
});
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) => {
if (url.includes("/api/ai/info")) {
return jsonResponse({
model: url.includes("atlas-genius") ? "genius-model" : "smart-model",
model: url.includes("atlas-smart") ? "smart-model" : "quick-model",
gpu: "titan-24",
node: "titan-24",
endpoint: "https://ai.example.dev/chat",
@ -76,18 +76,16 @@ describe("AI chat view", () => {
const wrapper = mount(AiView);
await flushPromises();
expect(wrapper.text()).not.toContain("Atlas Quick");
expect(wrapper.text()).toContain("smart-model");
expect(wrapper.text()).toContain("quick-model");
expect(wrapper.text()).toContain("titan-24");
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('"profile":"atlas-smart"'));
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();
expect(wrapper.text()).toContain("genius-model");
expect(wrapper.text()).toContain("smart-model");
Object.defineProperty(navigator, "clipboard", {
configurable: true,
@ -102,7 +100,7 @@ describe("AI chat view", () => {
it("sends JSON chat requests and reveals typed responses", async () => {
const bodies = [];
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));
return jsonResponse({ reply: "typed assistant response", latency_ms: 42 });
});
@ -119,10 +117,10 @@ describe("AI chat view", () => {
expect(wrapper.text()).toContain("42 ms");
expect(bodies[0]).toMatchObject({
message: "hello atlas",
profile: "atlas-smart",
conversation_id: expect.stringContaining("atlas-smart"),
profile: "atlas-quick",
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("form").trigger("submit.prevent");

View File

@ -53,9 +53,6 @@ describe("frontend entrypoint and router", () => {
expect(router.routes.map((route) => route.path)).toEqual([
"/",
"/about",
"/dev",
"/:focus(devops|developer|sdet)",
"/:focus(devops|developer|sdet)/about",
"/ai",
"/ai/chat",
"/ai/roadmap",
@ -66,9 +63,6 @@ describe("frontend entrypoint and router", () => {
"/onboarding",
]);
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();
});
});

View File

@ -1,22 +1,10 @@
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 { auth } from "../../../frontend/src/auth.js";
import { homepageContentForMode, normalizeHomepageFocus } from "../../../frontend/src/data/homepageContent.js";
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() {
auth.ready = true;
auth.enabled = true;
@ -30,7 +18,6 @@ function resetAuth() {
describe("HomeView", () => {
beforeEach(() => {
mockRoute = { meta: { homepageFocus: "sdet" }, params: {}, query: {} };
global.fetch = jest.fn(async (resource) => {
const url = typeof resource === "string" ? resource : resource?.url || "";
if (url.includes("/api/account/member-state")) {
@ -55,7 +42,7 @@ describe("HomeView", () => {
it("renders the professional homepage while logged out", () => {
resetAuth();
const wrapper = mount(HomeView, {
const wrapper = shallowMount(HomeView, {
global: {
stubs: {
RouterLink: RouterLinkStub,
@ -85,14 +72,14 @@ describe("HomeView", () => {
});
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("View Résumé");
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("Testing, Automation, and Platform Work");
expect(wrapper.text()).toContain("From 0 to 100");
expect(wrapper.text()).toContain("Platform Showcase");
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()).toContain("family, friends, and the lucky few");
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.find("a[href^='mailto:brad@bstein.dev']").exists()).toBe(true);
expect(wrapper.findComponent(PlatformSection).exists()).toBe(true);
expect(wrapper.text()).toContain("Atlas cluster");
expect(wrapper.text()).toContain("Most requested service");
expect(wrapper.text()).toContain("Atlas platform");
expect(wrapper.text()).toContain("Most active service");
expect(wrapper.text()).toContain("Nextcloud");
expect(wrapper.text()).toContain("Outpost hosts");
expect(wrapper.text()).toContain("2 of 2 online");
expect(wrapper.text()).toContain("Database machine");
expect(wrapper.text()).toContain("Jumphost");
expect(wrapper.text()).toContain("Dedicated hosts");
expect(wrapper.text()).toContain("2 of 2 responding");
expect(wrapper.text()).toContain("Responding");
});
it("shows a calm fallback when public status is unavailable", () => {
resetAuth();
const wrapper = mount(HomeView, {
const wrapper = shallowMount(HomeView, {
global: {
stubs: {
RouterLink: RouterLinkStub,
@ -145,7 +130,7 @@ describe("HomeView", () => {
auth.username = "ada";
auth.email = "ada@example.dev";
const wrapper = mount(HomeView, {
const wrapper = shallowMount(HomeView, {
global: {
stubs: {
RouterLink: RouterLinkStub,
@ -165,50 +150,6 @@ describe("HomeView", () => {
expect(wrapper.text()).not.toContain("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", () => {

View File

@ -20,7 +20,7 @@ describe("sample data builders", () => {
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 === "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.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");

View File

@ -1,6 +1,5 @@
import { afterEach, describe, expect, it, jest } from "@jest/globals";
import { flushPromises, mount, shallowMount } from "@vue/test-utils";
import { nextTick } from "vue";
import axios from "axios";
@ -27,7 +26,6 @@ jest.mock("vue-router", () => ({
props: ["to"],
template: "<a :href=\"typeof to === 'string' ? to : '#'\" @click=\"$emit('click', $event)\"><slot /></a>",
},
useRoute: () => ({ meta: { homepageFocus: "sdet" }, params: {}, query: {} }),
}), { virtual: true });
describe("static shell views and components", () => {
@ -45,7 +43,7 @@ describe("static shell views and components", () => {
const about = shallowMount(AboutView);
expect(about.text()).toContain("About Me");
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("IBM Cloud");
expect(about.text()).toContain("TradeHat");
@ -125,51 +123,17 @@ describe("static shell views and components", () => {
const logout = jest.spyOn(authModule, "logout").mockImplementation(() => {});
jest.spyOn(authModule, "login").mockImplementation(() => {});
const target = document.createElement("div");
document.body.appendChild(target);
const wrapper = mount(TopBar, { attachTo: target });
try {
const wrapper = mount(TopBar);
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("Register");
expect(wrapper.text()).not.toContain("Reset Password");
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");
expect(wrapper.find("nav").classes()).toContain("open");
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;
await flushPromises();
@ -177,10 +141,6 @@ describe("static shell views and components", () => {
expect(wrapper.text()).toContain("Open Services");
await wrapper.findAll("button.button").at(-1).trigger("click");
expect(logout).toHaveBeenCalled();
} finally {
wrapper.unmount();
target.remove();
}
});
it("loads Monero status and handles API failures", async () => {
@ -222,13 +182,6 @@ describe("static shell views and components", () => {
});
await flushPromises();
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();