@@ -789,19 +795,20 @@ h1 {
.account-grid {
display: grid;
- grid-template-columns: 1fr 1fr;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 12px;
margin-top: 12px;
align-items: stretch;
}
+.account-column,
.account-stack {
display: grid;
- grid-template-rows: repeat(2, minmax(0, 1fr));
gap: 12px;
- height: 100%;
+ align-content: start;
}
+.account-column .module,
.account-stack .module {
min-height: 0;
display: flex;
@@ -944,6 +951,10 @@ button.primary {
.account-stack .module {
flex: none;
}
+
+ .account-column .module {
+ flex: none;
+ }
}
.admin {
diff --git a/frontend/src/views/OnboardingView.vue b/frontend/src/views/OnboardingView.vue
index bfa2ef9..cb3bd0c 100644
--- a/frontend/src/views/OnboardingView.vue
+++ b/frontend/src/views/OnboardingView.vue
@@ -451,6 +451,49 @@
+
You're ready
@@ -537,6 +580,112 @@ const extraSteps = [
},
];
+// Guide images: drop files into src/assets/onboarding//01-step.png to auto-load and order.
+const guideShots = buildGuideShots(
+ import.meta.glob("../assets/onboarding/**/*.{png,jpg,jpeg,webp}", { eager: true, as: "url" }),
+);
+
+const mobileGuides = [
+ {
+ id: "vaultwarden",
+ title: "Vaultwarden",
+ app: "Bitwarden",
+ description: "Password manager with autofill across devices.",
+ url: "https://vault.bstein.dev",
+ fields: [
+ { label: "Server", value: "vault.bstein.dev" },
+ { label: "Login", value: "Atlas email + password" },
+ ],
+ },
+ {
+ id: "elementx",
+ title: "Element X",
+ app: "Matrix chat",
+ description: "Secure chat for Othrys rooms and direct messages.",
+ url: "https://live.bstein.dev",
+ fields: [
+ { label: "Homeserver", value: "live.bstein.dev" },
+ { label: "Login", value: "Atlas username + password" },
+ ],
+ },
+ {
+ id: "wger",
+ title: "Wger",
+ app: "Health tracking",
+ description: "Workout + nutrition tracking for Atlas.",
+ url: "https://health.bstein.dev",
+ fields: [
+ { label: "Server", value: "health.bstein.dev" },
+ { label: "Login", value: "Use Account page credentials" },
+ ],
+ },
+ {
+ id: "firefly",
+ title: "Firefly III",
+ app: "Abacus",
+ description: "Personal finance manager for budgets and spending.",
+ url: "https://money.bstein.dev",
+ fields: [
+ { label: "Server", value: "money.bstein.dev" },
+ { label: "Login", value: "Use Account page credentials" },
+ ],
+ },
+ {
+ id: "nextcloud",
+ title: "Nextcloud",
+ app: "Files + mail",
+ description: "Files, calendar, and mail on the go.",
+ url: "https://cloud.bstein.dev",
+ fields: [
+ { label: "Server", value: "cloud.bstein.dev" },
+ { label: "Login", value: "Atlas username + password" },
+ ],
+ },
+ {
+ id: "jellyfin",
+ title: "Jellyfin",
+ app: "Media streaming",
+ description: "Personal media library for Atlas.",
+ url: "https://stream.bstein.dev",
+ fields: [
+ { label: "Server", value: "stream.bstein.dev" },
+ { label: "Login", value: "Atlas username + password" },
+ ],
+ },
+];
+
+function buildGuideShots(rawShots) {
+ const grouped = {};
+ for (const [path, url] of Object.entries(rawShots)) {
+ const normalized = path.replace(/\\/g, "/");
+ const parts = normalized.split("/assets/onboarding/");
+ if (parts.length < 2) continue;
+ const [guideId, file] = parts[1].split("/");
+ if (!guideId || !file) continue;
+ const order = guideOrder(file);
+ const label = guideLabel(file);
+ if (!grouped[guideId]) grouped[guideId] = [];
+ grouped[guideId].push({ url, order, label, file });
+ }
+ Object.values(grouped).forEach((shots) => {
+ shots.sort((a, b) => (a.order - b.order) || a.file.localeCompare(b.file));
+ });
+ return grouped;
+}
+
+function guideOrder(filename) {
+ const prefix = filename.match(/^(\d{1,3})/);
+ if (prefix) return Number(prefix[1]);
+ const step = filename.match(/step[-_]?(\d{1,3})/i);
+ if (step) return Number(step[1]);
+ return Number.MAX_SAFE_INTEGER;
+}
+
+function guideLabel(filename) {
+ const base = filename.replace(/\.(png|jpe?g|webp)$/i, "");
+ return base.replace(/^\d+[-_]?/, "").replace(/[-_]/g, " ").trim();
+}
+
function statusLabel(value) {
const key = (value || "").trim();
if (key === "pending_email_verification") return "confirm email";
@@ -1228,6 +1377,73 @@ button.secondary {
gap: 6px;
}
+.mobile-guides {
+ margin-top: 18px;
+ display: grid;
+ gap: 12px;
+}
+
+.guide-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 12px;
+}
+
+.guide-card {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.guide-actions {
+ margin-top: 10px;
+ display: flex;
+ gap: 10px;
+}
+
+.guide-link {
+ color: rgba(125, 208, 255, 0.9);
+ text-decoration: none;
+}
+
+.guide-details {
+ margin-top: 10px;
+ border-radius: 12px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: rgba(0, 0, 0, 0.16);
+ padding: 10px 12px;
+}
+
+.guide-details summary {
+ cursor: pointer;
+ color: var(--text-muted);
+}
+
+.guide-images {
+ margin-top: 12px;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
+ gap: 10px;
+}
+
+.guide-shot {
+ margin: 0;
+ display: grid;
+ gap: 6px;
+}
+
+.guide-shot img {
+ width: 100%;
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: rgba(0, 0, 0, 0.25);
+}
+
+.guide-shot figcaption {
+ color: var(--text-muted);
+ font-size: 12px;
+}
+
@media (max-width: 560px) {
.recovery-verify {
flex-direction: column;