* feat(home): status frame on /home — last sync, sessions, prompts, tokens, projects
Adds the homepage status frame: a 5-card row above the install-hero /
offboard-strip on /home showing the calling user's Last sync (their
last `agnes pull`), Sessions, Prompts, Tokens used, and Projects worked
on, with a 24h/7d pill toggle.
Backed by `GET /api/me/home-stats?window=` (one DuckDB CTE joining
`users` + `usage_session_summary` + `usage_events`) and SSR'd from the
same `compute_home_stats` helper on initial paint so there's no
spinner. The window toggle is the only JS-driven path.
Side surfaces:
- `GET /api/sync/manifest` now stamps `users.last_pull_at` so
`agnes pull` (and the Claude Code SessionStart hook that wraps it)
imprints the analyst's last sync time for the new card.
- `usage_session_summary` gains four BIGINT token counters
(input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens)
summed from JSONL `message.usage.*` per assistant turn.
- `USAGE_PROCESSOR_VERSION` bumps 1 → 2 so the session-pipeline
reprocess loop invalidates stale summaries and backfills tokens
on the next tick.
Schema migration v43 → v44 is idempotent ALTERs (last_pull_at +
4 token columns) — fresh installs receive them from `_SYSTEM_SCHEMA`,
upgrade path runs `_v43_to_v44`. Defaults (NULL / 0) backfill
existing rows cleanly.
9 new tests in tests/test_home_stats.py cover the migration,
endpoint shapes (24h/7d/unknown/empty/missing-user), and the
manifest-side last_pull_at bump.
* docs(CHANGELOG): homepage status frame entries under [Unreleased]
The post-rebase release-cut now belongs to whichever PR lands next
after main rolled to 0.54.9. This PR logs its bullets under
[Unreleased] (Added: homepage status frame, per-user pull tracking,
token counters; Changed: schema v43 → v44 migration) so they ride
out with the next release-cut.
* fix(tests): bump test_schema_v42_migration asserts to v44
CI failed because tests/test_schema_v42_migration.py hardcoded
`assert SCHEMA_VERSION == 43` and `assert v == 43` after init.
v44 (homepage stats frame backing columns) was introduced in the
preceding feat commit; this aligns the existing v42-era migration
tests with the new schema version.
* feat(home): gate status frame on operator flag + user.onboarded
Two gates on the homepage status frame:
1. **Operator master switch** — `get_home_status_frame_visibility()` in
app/instance_config.py mirrors the existing `get_home_automode_visibility()`
shape: env var `AGNES_HOME_SHOW_STATUS_FRAME` > yaml
`instance.home.show_status_frame` > default `True`. Cautious-rollout
instances can disable the frame without forking; the yaml example
documents both knobs.
2. **Onboarded gate** — the template only renders the frame when the
caller's `users.onboarded` is true. First-day users see a clean
install-hero before all-zero stat cards; the frame appears
automatically on the next render after `agnes init` POSTs
`/api/me/onboarded`.
Router skips the `compute_home_stats` DB read entirely when either
gate is closed; `home_stats` arrives at the template as None in that
branch and the `{% if %}` shortcuts the include.
Why both gates: PostHog feature flags evaluated and rejected — this
codebase uses PostHog for analytics capture only, not feature gating;
adding a per-user feature_enabled() call on the /home critical path
would couple the homepage render to a remote eval and still require
an admin master switch. The onboarded gate is a UX coherence rule
layered on top of the operator switch, not an A/B test signal.
3 new tests in test_home_stats.py cover the env-var resolution
(falsey values + default-true). The yaml example gets a `home:`
block documenting both `show_automode` (pre-existing flag, was
undocumented in the example) and `show_status_frame`.
2099 lines
78 KiB
HTML
2099 lines
78 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Setup — {{ instance_name or "AI Data Analyst" }}{% endblock %}
|
||
|
||
{% block content %}
|
||
{% include "_page_chrome.html" %}
|
||
<style>
|
||
.home-mock {
|
||
--hp-primary: #0073D1;
|
||
--hp-primary-dark: #0056A3;
|
||
--hp-primary-light: #E6F3FC;
|
||
--hp-border: #E5E7EB;
|
||
--hp-border-light: #F3F4F6;
|
||
--hp-text-primary: #111827;
|
||
--hp-text-secondary: #6B7280;
|
||
--hp-text-muted: #9CA3AF;
|
||
--hp-orange: #C2410C;
|
||
--hp-orange-light: #FED7AA;
|
||
--hp-font-mono: ui-monospace, "SF Mono", Consolas, monospace;
|
||
color: var(--hp-text-primary);
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
.home-mock * { box-sizing: border-box; }
|
||
|
||
.home-mock .install-hero {
|
||
position: relative;
|
||
background: linear-gradient(135deg, #0073D1 0%, #0056A3 100%);
|
||
color: white;
|
||
border-radius: 16px;
|
||
padding: 38px 40px;
|
||
margin-bottom: 22px;
|
||
box-shadow: 0 8px 24px rgba(0, 86, 163, 0.18);
|
||
}
|
||
.home-mock .install-hero-close {
|
||
position: absolute;
|
||
top: 14px;
|
||
right: 14px;
|
||
width: 32px;
|
||
height: 32px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.14);
|
||
color: white;
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background 0.15s;
|
||
}
|
||
.home-mock .install-hero-close:hover,
|
||
.home-mock .install-hero-close:focus-visible {
|
||
background: rgba(255, 255, 255, 0.28);
|
||
outline: none;
|
||
}
|
||
.home-mock .offboard-strip {
|
||
margin: 0 0 22px;
|
||
padding: 10px 14px;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 10px;
|
||
background: var(--hp-border-light);
|
||
color: var(--hp-text-secondary);
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
.home-mock .offboard-strip button {
|
||
border: 1px solid var(--hp-border);
|
||
background: white;
|
||
border-radius: 6px;
|
||
padding: 4px 10px;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
}
|
||
.home-mock .offboard-strip button:hover { background: var(--hp-border-light); }
|
||
.home-mock .install-hero .eyebrow {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.8px;
|
||
opacity: 0.85;
|
||
margin-bottom: 10px;
|
||
}
|
||
.home-mock .install-hero h1 {
|
||
font-size: 30px;
|
||
font-weight: 600;
|
||
letter-spacing: -0.4px;
|
||
margin-bottom: 12px;
|
||
line-height: 1.2;
|
||
color: white;
|
||
}
|
||
.home-mock .install-hero .lead {
|
||
font-size: 15px;
|
||
opacity: 0.94;
|
||
line-height: 1.6;
|
||
margin-bottom: 22px;
|
||
}
|
||
.home-mock .install-hero .lead strong { font-weight: 600; }
|
||
|
||
/* ── Getting Started + Overview + Usage modes (added in PR #289) ───
|
||
Three new content cards rendered between the install-hero and the
|
||
connector tiles. Getting Started + Usage modes ship in OSS;
|
||
Overview body comes from instance.overview yaml (operator-owned),
|
||
hidden when unset. Generic dismiss pattern: any element with
|
||
`<button class="home-card-close" data-dismiss-key="...">` gets
|
||
per-device localStorage dismissibility for free. */
|
||
|
||
.home-mock .home-getting-started,
|
||
.home-mock .home-overview,
|
||
.home-mock .home-usage {
|
||
position: relative;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 22px 24px;
|
||
margin-bottom: 18px;
|
||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
|
||
}
|
||
/* Getting Started uses <details> for native collapsed-by-default
|
||
behaviour. The summary owns the padding when collapsed; row layout
|
||
reveals on expand. Chevron rotates 90deg when open. */
|
||
.home-mock details.home-getting-started {
|
||
padding: 0;
|
||
}
|
||
.home-mock .home-gs-summary {
|
||
list-style: none;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px 24px;
|
||
user-select: none;
|
||
}
|
||
.home-mock .home-gs-summary::-webkit-details-marker { display: none; }
|
||
.home-mock .home-gs-summary-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-gs-summary-hint {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
}
|
||
.home-mock .home-gs-summary-chev {
|
||
font-size: 18px;
|
||
color: var(--hp-text-muted);
|
||
transition: transform 0.15s;
|
||
}
|
||
.home-mock details.home-getting-started[open] .home-gs-summary-chev {
|
||
transform: rotate(90deg);
|
||
}
|
||
.home-mock details.home-getting-started[open] {
|
||
padding-bottom: 18px;
|
||
}
|
||
.home-mock details.home-getting-started[open] .home-gs-item {
|
||
margin-left: 24px;
|
||
margin-right: 24px;
|
||
}
|
||
/* Install-hero is the scroll target for Getting Started's first row.
|
||
Offset the anchor jump by the 72px sticky .app-header height + a
|
||
bit of breathing room so the hero's eyebrow lands cleanly under
|
||
the header bar. */
|
||
.home-mock .install-hero { scroll-margin-top: 88px; }
|
||
|
||
.home-mock .home-overview > h2,
|
||
.home-mock .home-usage > header h2 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0 0 6px;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-usage > header p {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
margin: 0 0 14px;
|
||
}
|
||
|
||
.home-mock .home-card-close {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
width: 28px;
|
||
height: 28px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: transparent;
|
||
color: var(--hp-text-muted);
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.home-mock .home-card-close:hover,
|
||
.home-mock .home-card-close:focus-visible {
|
||
background: var(--hp-border-light);
|
||
color: var(--hp-text-primary);
|
||
outline: none;
|
||
}
|
||
|
||
.home-mock .home-gs-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 14px;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 10px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
margin-top: 8px;
|
||
transition: border-color 0.15s, background 0.15s;
|
||
}
|
||
.home-mock .home-gs-item:hover {
|
||
border-color: var(--hp-primary);
|
||
background: var(--hp-primary-light);
|
||
}
|
||
.home-mock .home-gs-item .ico {
|
||
font-size: 20px;
|
||
flex: 0 0 24px;
|
||
text-align: center;
|
||
}
|
||
.home-mock .home-gs-item .text {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.home-mock .home-gs-item .text strong {
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-gs-item .text .desc {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
}
|
||
.home-mock .home-gs-item .arrow {
|
||
color: var(--hp-text-muted);
|
||
font-size: 16px;
|
||
}
|
||
|
||
.home-mock .home-overview-body {
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-overview-body p { margin: 0 0 10px; }
|
||
.home-mock .home-overview-body p:last-child { margin-bottom: 0; }
|
||
.home-mock .home-overview-body a { color: var(--hp-primary); }
|
||
|
||
.home-mock .home-usage-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.home-mock .home-usage-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
padding: 14px;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 10px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: border-color 0.15s, background 0.15s;
|
||
}
|
||
/* Hover affordance only on the anchor variants (VS Code, Claude
|
||
Desktop). The Terminal tile renders as a plain <div> — the setup
|
||
hero above already covers the terminal install path, so a
|
||
click-through from here would round-trip to content the user just
|
||
scrolled past. */
|
||
.home-mock a.home-usage-item:hover {
|
||
border-color: var(--hp-primary);
|
||
background: var(--hp-primary-light);
|
||
}
|
||
.home-mock .home-usage-item .ico { font-size: 20px; }
|
||
.home-mock .home-usage-item strong { font-weight: 600; }
|
||
.home-mock .home-usage-item span {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.5;
|
||
}
|
||
.home-mock .home-usage-foot {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
margin: 0;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.home-mock .home-usage-grid { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.home-mock .what-is {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 14px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.home-mock .what-is-item {
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 18px 22px;
|
||
color: inherit;
|
||
text-decoration: none;
|
||
display: block;
|
||
transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
|
||
}
|
||
.home-mock a.what-is-item:hover {
|
||
border-color: var(--hp-primary);
|
||
box-shadow: 0 4px 12px rgba(0, 115, 209, 0.10);
|
||
transform: translateY(-1px);
|
||
}
|
||
.home-mock .what-is-item .ico { font-size: 22px; margin-bottom: 8px; display: block; }
|
||
.home-mock .what-is-item .ttl {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
margin-bottom: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
.home-mock .what-is-item .ttl .arrow {
|
||
color: var(--hp-primary);
|
||
font-weight: 600;
|
||
transition: transform 0.15s ease;
|
||
}
|
||
.home-mock a.what-is-item:hover .ttl .arrow {
|
||
transform: translateX(2px);
|
||
}
|
||
.home-mock .what-is-item .desc {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.home-mock .look-around-lead {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
margin: -4px 0 12px;
|
||
}
|
||
.home-mock .look-around-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.home-mock .connector-tiles {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 26px;
|
||
}
|
||
.home-mock .connector-tile {
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 20px 22px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.home-mock .connector-tile .ico {
|
||
font-size: 24px;
|
||
margin-bottom: 10px;
|
||
line-height: 1;
|
||
}
|
||
.home-mock .connector-tile .ttl {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
margin-bottom: 6px;
|
||
}
|
||
.home-mock .connector-tile .desc {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
margin-bottom: 14px;
|
||
flex: 1;
|
||
}
|
||
.home-mock .connector-tile .connector-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.home-mock .connector-tile .connector-copy {
|
||
align-self: flex-start;
|
||
}
|
||
|
||
.home-mock .install-block {
|
||
background: rgba(15, 23, 42, 0.55);
|
||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||
border-radius: 12px;
|
||
padding: 18px 20px;
|
||
}
|
||
.home-mock .install-block + .install-block { margin-top: 14px; }
|
||
.home-mock .os-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
margin-bottom: 8px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.10);
|
||
}
|
||
.home-mock .os-tab {
|
||
background: transparent;
|
||
border: none;
|
||
color: rgba(255, 255, 255, 0.60);
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
padding: 8px 14px;
|
||
cursor: pointer;
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -1px;
|
||
font-family: inherit;
|
||
}
|
||
.home-mock .os-tab:hover { color: rgba(255, 255, 255, 0.85); }
|
||
.home-mock .os-tab.is-active {
|
||
color: white;
|
||
border-bottom-color: #FBBF24;
|
||
}
|
||
/* `[hidden]` UA stylesheet has lower specificity than `.install-cmd { display: flex }`,
|
||
so we need an explicit override to actually hide the inactive tab panel. */
|
||
.home-mock .install-cmd[hidden] { display: none; }
|
||
.home-mock .install-block .label {
|
||
font-size: 11px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.6px;
|
||
opacity: 0.78;
|
||
margin-bottom: 10px;
|
||
font-weight: 600;
|
||
}
|
||
.home-mock .install-cmd {
|
||
background: #0F172A;
|
||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||
border-radius: 8px;
|
||
padding: 14px 18px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 13.5px;
|
||
color: #FBBF24;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
}
|
||
.home-mock .install-cmd .multiline { display: block; line-height: 1.7; white-space: pre-wrap; flex: 1; }
|
||
.home-mock .install-cmd .copy-btn {
|
||
background: rgba(255, 255, 255, 0.10);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
padding: 7px 14px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
flex-shrink: 0;
|
||
}
|
||
.home-mock .install-cmd .copy-btn:hover { background: rgba(255, 255, 255, 0.18); }
|
||
|
||
.home-mock .install-note {
|
||
font-size: 12.5px;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
margin-top: 12px;
|
||
line-height: 1.55;
|
||
}
|
||
.home-mock .install-note code,
|
||
.home-mock .install-note > code {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
}
|
||
/* Links inside the blue install-hero need a non-blue color — the default
|
||
`<a>` style elsewhere on /home renders as `var(--hp-primary)` blue,
|
||
which is invisible against the hero's blue background. Use the same
|
||
high-contrast white-with-underline pattern the lead paragraph uses,
|
||
and bump hover to full white so the affordance stays obvious. */
|
||
.home-mock .install-hero a {
|
||
color: #ffffff;
|
||
text-decoration: underline;
|
||
text-decoration-color: rgba(255, 255, 255, 0.6);
|
||
text-underline-offset: 2px;
|
||
}
|
||
.home-mock .install-hero a:hover,
|
||
.home-mock .install-hero a:focus {
|
||
color: #ffffff;
|
||
text-decoration-color: #ffffff;
|
||
}
|
||
|
||
.home-mock .automode-card {
|
||
margin: 22px 0;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 18px 22px;
|
||
}
|
||
.home-mock .automode-head {
|
||
display: flex;
|
||
gap: 14px;
|
||
align-items: flex-start;
|
||
margin-bottom: 14px;
|
||
}
|
||
.home-mock .automode-head .ico {
|
||
font-size: 22px;
|
||
flex-shrink: 0;
|
||
line-height: 1;
|
||
}
|
||
.home-mock .automode-head h3 {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 4px;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .automode-head p {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
margin: 0;
|
||
}
|
||
.home-mock .automode-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 14px;
|
||
}
|
||
.home-mock .automode-step {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: flex-start;
|
||
padding: 12px;
|
||
background: var(--hp-border-light);
|
||
border-radius: 8px;
|
||
}
|
||
.home-mock .automode-step .num {
|
||
width: 22px; height: 22px;
|
||
border-radius: 50%;
|
||
background: var(--hp-primary-light);
|
||
color: var(--hp-primary);
|
||
flex-shrink: 0;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-weight: 700; font-size: 11px;
|
||
}
|
||
.home-mock .automode-step .step-text {
|
||
flex: 1;
|
||
font-size: 12.5px;
|
||
line-height: 1.5;
|
||
color: var(--hp-text-secondary);
|
||
}
|
||
.home-mock .automode-step .step-text strong {
|
||
display: block;
|
||
color: var(--hp-text-primary);
|
||
margin-bottom: 4px;
|
||
font-size: 13px;
|
||
}
|
||
.home-mock .automode-step kbd {
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-bottom-width: 2px;
|
||
border-radius: 4px;
|
||
padding: 1px 6px;
|
||
font-size: 11px;
|
||
font-family: var(--hp-font-mono);
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .automode-step code {
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
background: white;
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
color: var(--hp-text-primary);
|
||
border: 1px solid var(--hp-border);
|
||
}
|
||
.home-mock .automode-code {
|
||
background: #0F172A;
|
||
color: #FBBF24;
|
||
border-radius: 6px;
|
||
padding: 8px 10px;
|
||
margin-top: 6px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
line-height: 1.5;
|
||
overflow-x: auto;
|
||
white-space: pre;
|
||
}
|
||
.home-mock .automode-foot {
|
||
margin-top: 12px;
|
||
padding-top: 10px;
|
||
border-top: 1px solid var(--hp-border-light);
|
||
font-size: 12px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.5;
|
||
}
|
||
.home-mock .automode-foot a { color: var(--hp-primary); text-decoration: underline; }
|
||
.home-mock .automode-foot code {
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11px;
|
||
background: var(--hp-border-light);
|
||
padding: 1px 4px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.home-mock .section-label {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.6px;
|
||
color: var(--hp-text-secondary);
|
||
margin: 30px 0 12px;
|
||
}
|
||
|
||
.home-mock .grid-2 {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 18px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.home-mock .card {
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 22px;
|
||
}
|
||
.home-mock .card h3 { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
|
||
.home-mock .card p { font-size: 13px; color: var(--hp-text-secondary); line-height: 1.55; margin-bottom: 12px; }
|
||
.home-mock .card-list { list-style: none; padding: 0; margin: 0; }
|
||
.home-mock .card-list li {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
padding: 8px 0;
|
||
font-size: 13px;
|
||
border-bottom: 1px solid var(--hp-border-light);
|
||
}
|
||
.home-mock .card-list li:last-child { border-bottom: none; }
|
||
.home-mock .card-list .num {
|
||
width: 22px; height: 22px;
|
||
border-radius: 50%;
|
||
background: var(--hp-primary-light);
|
||
color: var(--hp-primary);
|
||
flex-shrink: 0;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-weight: 700; font-size: 11px;
|
||
}
|
||
.home-mock .card-list .step-text { flex: 1; line-height: 1.55; }
|
||
.home-mock .card-list .step-text strong { display: block; margin-bottom: 2px; }
|
||
.home-mock .card-list .step-text code,
|
||
.home-mock .card-mini-cmd code {
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
background: var(--hp-border-light);
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
|
||
.home-mock .card-mini-cmd {
|
||
margin-top: 6px;
|
||
padding: 8px 10px;
|
||
background: #0F172A;
|
||
color: #FBBF24;
|
||
border-radius: 6px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
line-height: 1.5;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
.home-mock .card-mini-cmd code {
|
||
background: transparent;
|
||
color: inherit;
|
||
padding: 0;
|
||
flex: 1;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
.home-mock .connector-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
.home-mock .connector-head strong {
|
||
display: inline;
|
||
margin-bottom: 0;
|
||
}
|
||
.home-mock .connector-copy {
|
||
background: var(--hp-primary);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 5px 12px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
flex-shrink: 0;
|
||
}
|
||
.home-mock .connector-copy:hover { background: var(--hp-primary-dark); }
|
||
.home-mock .connector-copy.copied { background: #047857; }
|
||
.home-mock details.connector-preview {
|
||
margin-top: 6px;
|
||
}
|
||
.home-mock details.connector-preview > summary {
|
||
cursor: pointer;
|
||
list-style: none;
|
||
font-size: 11.5px;
|
||
color: var(--hp-text-secondary);
|
||
padding: 2px 0;
|
||
user-select: none;
|
||
}
|
||
.home-mock details.connector-preview > summary::-webkit-details-marker { display: none; }
|
||
.home-mock details.connector-preview > summary::before {
|
||
content: "▸ ";
|
||
margin-right: 2px;
|
||
}
|
||
.home-mock details.connector-preview[open] > summary::before { content: "▾ "; }
|
||
.home-mock details.connector-preview > summary:hover { color: var(--hp-text-primary); }
|
||
.home-mock details.connector-preview .card-mini-cmd { margin-top: 6px; }
|
||
|
||
.home-mock .explore-list { list-style: none; padding: 0; margin: 0; }
|
||
.home-mock .explore-list li + li { margin-top: 8px; }
|
||
.home-mock .explore-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 14px;
|
||
background: var(--hp-border-light);
|
||
border: 1px solid transparent;
|
||
border-radius: 8px;
|
||
text-decoration: none;
|
||
color: var(--hp-text-primary);
|
||
transition: border-color 0.15s ease, background 0.15s ease;
|
||
}
|
||
.home-mock .explore-item:hover {
|
||
border-color: var(--hp-primary);
|
||
background: var(--hp-primary-light);
|
||
}
|
||
.home-mock .explore-item .ico {
|
||
font-size: 22px;
|
||
flex-shrink: 0;
|
||
width: 28px;
|
||
text-align: center;
|
||
}
|
||
.home-mock .explore-item .explore-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.45;
|
||
}
|
||
.home-mock .explore-item .explore-text strong {
|
||
display: block;
|
||
color: var(--hp-text-primary);
|
||
font-size: 13.5px;
|
||
font-weight: 600;
|
||
margin-bottom: 2px;
|
||
}
|
||
.home-mock .explore-item .arrow {
|
||
color: var(--hp-primary);
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.home-mock .advanced-pointer {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
margin-top: 10px;
|
||
padding: 16px 22px;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-left: 4px solid var(--hp-primary);
|
||
border-radius: 12px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: background 0.15s ease, border-color 0.15s ease;
|
||
}
|
||
.home-mock .advanced-pointer:hover {
|
||
background: var(--hp-primary-light);
|
||
border-color: var(--hp-primary);
|
||
}
|
||
.home-mock .advanced-pointer .ico {
|
||
font-size: 22px;
|
||
flex-shrink: 0;
|
||
line-height: 1;
|
||
}
|
||
.home-mock .advanced-pointer-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
}
|
||
.home-mock .advanced-pointer-text strong {
|
||
display: block;
|
||
color: var(--hp-text-primary);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 2px;
|
||
}
|
||
.home-mock .advanced-pointer .arrow {
|
||
color: var(--hp-primary);
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.home-mock .setup-cta-lead {
|
||
font-size: 13px;
|
||
color: rgba(255, 255, 255, 0.82);
|
||
margin-bottom: 14px;
|
||
line-height: 1.55;
|
||
}
|
||
.home-mock .setup-cta-lead code {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
}
|
||
.home-mock .setup-cta-row {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 14px;
|
||
}
|
||
.home-mock .btn-setup-primary {
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--hp-primary);
|
||
background: #FFFFFF;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 22px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
white-space: nowrap;
|
||
transition: background 0.15s ease;
|
||
}
|
||
.home-mock .btn-setup-primary:hover { background: #F0F7FF; }
|
||
.home-mock .btn-setup-primary.copied { background: #047857; color: #fff; }
|
||
.home-mock .btn-setup-primary[disabled] { opacity: 0.7; cursor: wait; }
|
||
.home-mock .setup-cta-meta {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.home-mock .setup-cta-hint {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.65);
|
||
}
|
||
.home-mock details.manual-fallback {
|
||
margin-top: 14px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.10);
|
||
padding-top: 12px;
|
||
}
|
||
.home-mock details.manual-fallback > summary {
|
||
cursor: pointer;
|
||
list-style: none;
|
||
font-size: 12.5px;
|
||
color: rgba(255, 255, 255, 0.78);
|
||
padding: 4px 0;
|
||
user-select: none;
|
||
}
|
||
.home-mock details.manual-fallback > summary::-webkit-details-marker { display: none; }
|
||
.home-mock details.manual-fallback > summary::before {
|
||
content: "▸ ";
|
||
display: inline-block;
|
||
transition: transform 0.15s ease;
|
||
margin-right: 4px;
|
||
}
|
||
.home-mock details.manual-fallback[open] > summary::before { content: "▾ "; }
|
||
.home-mock .manual-preview-wrap { margin-top: 10px; }
|
||
.home-mock .manual-preview-wrap .setup-preview-pre {
|
||
background: #0F172A;
|
||
color: #E2E8F0;
|
||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||
border-radius: 8px;
|
||
padding: 14px 16px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 12.5px;
|
||
line-height: 1.55;
|
||
overflow-x: auto;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
margin: 0;
|
||
}
|
||
/* Override the global `code { background: var(--bg); }` rule so the inner
|
||
<code> renders white-on-navy instead of dark-on-pale (unreadable). */
|
||
.home-mock .manual-preview-wrap .setup-preview-pre code,
|
||
.home-mock .manual-preview-wrap .setup-preview-code {
|
||
background: transparent;
|
||
color: #E2E8F0;
|
||
padding: 0;
|
||
border-radius: 0;
|
||
font-family: inherit;
|
||
font-size: inherit;
|
||
}
|
||
.home-mock .manual-preview-wrap .placeholder-token {
|
||
background: rgba(251, 191, 36, 0.20);
|
||
color: #FBBF24;
|
||
padding: 0 4px;
|
||
border-radius: 3px;
|
||
font-style: italic;
|
||
}
|
||
.home-mock .manual-preview-wrap .token-revealed {
|
||
background: rgba(16, 185, 129, 0.18);
|
||
color: #6EE7B7;
|
||
padding: 0 4px;
|
||
border-radius: 3px;
|
||
font-style: normal;
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* In-hero (dark) variant: shown for not-onboarded users underneath the
|
||
inline install steps inside the blue hero. */
|
||
.home-mock .self-mark {
|
||
margin-top: 16px;
|
||
font-size: 13px;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
.home-mock .self-mark button {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.30);
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
margin-left: 6px;
|
||
}
|
||
.home-mock .self-mark button:hover { background: rgba(255, 255, 255, 0.25); }
|
||
.home-mock .self-mark button:disabled { opacity: 0.5; cursor: default; }
|
||
.home-mock .self-mark .status { margin-left: 10px; font-style: italic; }
|
||
|
||
/* Onboarded "steps complete" badge — shown inside the blue hero in
|
||
place of the hidden Step 1 + Step 2 install-blocks. */
|
||
.home-mock .install-done {
|
||
margin-top: 4px;
|
||
margin-bottom: 4px;
|
||
padding: 12px 16px;
|
||
background: rgba(255, 255, 255, 0.10);
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
border-radius: 10px;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
font-size: 13px;
|
||
line-height: 1.55;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
}
|
||
.home-mock .install-done .check {
|
||
font-size: 16px;
|
||
line-height: 1.3;
|
||
flex-shrink: 0;
|
||
}
|
||
.home-mock .install-done strong { font-weight: 600; color: white; }
|
||
|
||
/* News perex (admin-edited) — bottom of /home.
|
||
Keeps the styling within the admin-defined class vocabulary
|
||
(callout, video-embed) so authors get a predictable surface.
|
||
Full-width grid classes are intentionally NOT included here —
|
||
they live on /news where the layout has more room. */
|
||
.home-mock .home-news {
|
||
margin-top: 28px;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
overflow: hidden; /* keep the green strip's corners rounded */
|
||
}
|
||
.home-mock .home-news-head {
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
padding: 12px 22px;
|
||
background: #D1FAE5; /* same green as .callout-success */
|
||
border-bottom: 1px solid #A7F3D0;
|
||
}
|
||
.home-mock .home-news-head h2 {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #047857; /* darker green for contrast on the band */
|
||
margin: 0;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.6px;
|
||
}
|
||
.home-mock .home-news-more {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #047857;
|
||
text-decoration: none;
|
||
}
|
||
.home-mock .home-news-more:hover { text-decoration: underline; }
|
||
.home-mock .home-news-body {
|
||
font-size: 14px;
|
||
line-height: 1.55;
|
||
color: var(--hp-text-primary);
|
||
padding: 18px 22px;
|
||
}
|
||
.home-mock .home-news-body p { margin: 0 0 8px; }
|
||
.home-mock .home-news-body p:last-child { margin-bottom: 0; }
|
||
|
||
/* Setup-collapsible: each post-install section (Step 3 auto-mode,
|
||
Connect-your-tools) is wrapped in a <details open> so it keyboard-
|
||
collapses without JS. Default state on /home: summary hidden, body
|
||
flat — visually identical to the pre-collapse layout.
|
||
|
||
When the user clicks "Minimize setup view" in the hero, JS sets
|
||
`data-setup-minimized="1"` on `.home-mock` and removes the `open`
|
||
attribute on each <details>. The summary then appears as a slim
|
||
gray bar; the body collapses; clicking the bar re-opens that one
|
||
section. localStorage persists the minimize state per device. */
|
||
.home-mock .setup-collapsible { margin-top: 22px; }
|
||
.home-mock .setup-collapsible > summary {
|
||
/* Default: summary is structural-only. The "flat" rendering is
|
||
handled by the inner blocks (.automode-card already provides its
|
||
own padding/border/background; the connect-your-tools section
|
||
starts with a .section-label-flat). */
|
||
display: none;
|
||
}
|
||
.home-mock .setup-collapsible > summary::-webkit-details-marker { display: none; }
|
||
|
||
/* Minimize ON: summary becomes a slim clickable bar; the inner body
|
||
inherits the existing block styling but tucks under the summary
|
||
when the <details> is closed. */
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible {
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
background: white;
|
||
overflow: hidden;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary {
|
||
list-style: none;
|
||
cursor: pointer;
|
||
padding: 14px 22px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
background: #F9FAFB;
|
||
border-bottom: 1px solid transparent;
|
||
transition: background 120ms ease;
|
||
user-select: none;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary:hover {
|
||
background: #F3F4F6;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .ico {
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .ttl { flex: 1; }
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .ttl small {
|
||
color: var(--hp-text-secondary);
|
||
font-weight: 400;
|
||
margin-left: 6px;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .chev {
|
||
font-size: 18px;
|
||
color: var(--hp-text-secondary);
|
||
transition: transform 160ms ease;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > summary {
|
||
border-bottom-color: var(--hp-border);
|
||
background: #F3F4F6;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > summary .chev {
|
||
transform: rotate(90deg);
|
||
}
|
||
/* Tighten the body padding so collapsed sections don't gain margin
|
||
when inside the white wrapper card. */
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > .automode-card,
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > .connector-tiles {
|
||
margin: 18px 22px 22px;
|
||
}
|
||
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > .section-label-flat {
|
||
padding: 14px 22px 0;
|
||
}
|
||
/* When a section's <details> is closed in minimize mode, the body
|
||
already auto-hides via the browser's <details> behaviour — no extra
|
||
rule needed. */
|
||
|
||
/* Minimize toggle in the blue hero, only present when onboarded. */
|
||
.home-mock .setup-minimize {
|
||
margin-top: 14px;
|
||
font-size: 13px;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
.home-mock .setup-minimize button {
|
||
background: rgba(255, 255, 255, 0.10);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||
padding: 6px 14px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
}
|
||
.home-mock .setup-minimize button:hover { background: rgba(255, 255, 255, 0.18); }
|
||
|
||
/* Callout, video-embed, news-hero, news-grid-*, news-cta — shared news
|
||
content vocabulary now lives in app/web/static/style-custom.css under
|
||
"News content vocabulary (shared)". Editing those rules in one place
|
||
updates /home perex, /news, and /admin/news preview together. */
|
||
|
||
.home-mock .install-done code {
|
||
background: rgba(15, 23, 42, 0.55);
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 12px;
|
||
color: #FBBF24;
|
||
}
|
||
|
||
/* ─────────────────────────────────────────────────────────────────
|
||
Onboarding polish — friction fixes from internal usability testing.
|
||
Each block is tagged with its priority (P0/P1/P2) — P0 resolves the
|
||
highest-frequency confusion, P2 is nice-to-have polish.
|
||
───────────────────────────────────────────────────────────────── */
|
||
|
||
/* P0-1 — "Don't have a terminal open?" disclosure inside Step 1's
|
||
blue install-block. Yellow accent matches Claude footer mode dots
|
||
so it reads as a hint, not an error. */
|
||
.home-mock details.terminal-howto {
|
||
margin-top: 10px;
|
||
padding: 8px 12px;
|
||
background: rgba(251, 191, 36, 0.10);
|
||
border: 1px solid rgba(251, 191, 36, 0.30);
|
||
border-radius: 8px;
|
||
}
|
||
.home-mock details.terminal-howto > summary {
|
||
cursor: pointer;
|
||
list-style: none;
|
||
font-size: 12.5px;
|
||
font-weight: 600;
|
||
color: #FBBF24;
|
||
user-select: none;
|
||
}
|
||
.home-mock details.terminal-howto > summary::-webkit-details-marker { display: none; }
|
||
.home-mock details.terminal-howto > summary::before { content: "▸ "; margin-right: 4px; }
|
||
.home-mock details.terminal-howto[open] > summary::before { content: "▾ "; }
|
||
.home-mock .terminal-howto-body {
|
||
margin-top: 10px;
|
||
font-size: 12.5px;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
line-height: 1.6;
|
||
}
|
||
.home-mock .terminal-howto-body p { margin: 0 0 6px; }
|
||
.home-mock .terminal-howto-body kbd {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border: 1px solid rgba(255, 255, 255, 0.30);
|
||
border-bottom-width: 2px;
|
||
border-radius: 4px;
|
||
padding: 1px 6px;
|
||
font-size: 11px;
|
||
font-family: var(--hp-font-mono);
|
||
color: white;
|
||
}
|
||
|
||
/* P0-3 — connector card time badge ("~5 min · self-serve") and the
|
||
post-copy "now paste into Step 2" hint with arrow back. */
|
||
.home-mock .connector-tile .ttl-row {
|
||
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
||
margin-bottom: 6px;
|
||
}
|
||
.home-mock .time-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
background: var(--hp-primary-light);
|
||
color: var(--hp-primary-dark);
|
||
flex-shrink: 0;
|
||
}
|
||
.home-mock .time-badge.is-warn {
|
||
background: #FEF3C7;
|
||
color: #92400E;
|
||
}
|
||
.home-mock .copy-next-hint {
|
||
font-size: 12px;
|
||
color: var(--hp-primary-dark);
|
||
background: var(--hp-primary-light);
|
||
padding: 6px 10px;
|
||
border-radius: 6px;
|
||
margin-top: 6px;
|
||
display: none;
|
||
align-items: center;
|
||
gap: 6px;
|
||
line-height: 1.45;
|
||
}
|
||
.home-mock .copy-next-hint.is-visible { display: inline-flex; }
|
||
.home-mock .copy-next-hint code {
|
||
background: rgba(0, 0, 0, 0.06);
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11px;
|
||
}
|
||
|
||
/* P1-8 — GWS gating note + Email-admin button. Yellow background
|
||
matches the same friction-level used by other "admin help likely"
|
||
surfaces (advanced setup warnings). */
|
||
.home-mock .gating-note {
|
||
font-size: 12px;
|
||
color: #92400E;
|
||
background: #FEF3C7;
|
||
border: 1px solid #FCD34D;
|
||
border-radius: 6px;
|
||
padding: 8px 10px;
|
||
margin-bottom: 10px;
|
||
line-height: 1.5;
|
||
}
|
||
.home-mock .email-admin {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
color: var(--hp-text-primary);
|
||
padding: 5px 12px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
align-self: flex-start;
|
||
margin-top: 6px;
|
||
}
|
||
.home-mock .email-admin:hover {
|
||
text-decoration: none;
|
||
border-color: var(--hp-primary);
|
||
color: var(--hp-primary-dark);
|
||
}
|
||
|
||
/* P1-6 — Auto-detect badge replaces the "Mark me as onboarded"
|
||
button as the primary affordance. Pulse dot signals it's actively
|
||
listening; the manual self-mark button stays as a fallback. */
|
||
.home-mock .auto-detect-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 16px;
|
||
background: rgba(255, 255, 255, 0.10);
|
||
border: 1px solid rgba(255, 255, 255, 0.20);
|
||
color: rgba(255, 255, 255, 0.92);
|
||
padding: 8px 14px;
|
||
border-radius: 8px;
|
||
font-size: 12.5px;
|
||
}
|
||
.home-mock .auto-detect-badge .pulse {
|
||
width: 8px; height: 8px;
|
||
border-radius: 50%;
|
||
background: #FBBF24;
|
||
box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.7);
|
||
animation: agnes-pulse 1.6s infinite;
|
||
}
|
||
.home-mock .auto-detect-badge code {
|
||
background: rgba(0, 0, 0, 0.18);
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
}
|
||
@keyframes agnes-pulse {
|
||
0% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.65); }
|
||
70% { box-shadow: 0 0 0 8px rgba(251, 191, 36, 0); }
|
||
100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0); }
|
||
}
|
||
|
||
/* P0-2 — Post-CTA modal with "where to paste" 3-step guide. Modal
|
||
markup lives at body level; this styles the backdrop + card. The
|
||
.home-mock prefix is intentionally NOT used so the modal is
|
||
render-isolated from the home page's CSS variables. */
|
||
.cta-modal-backdrop {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(15, 23, 42, 0.55);
|
||
-webkit-backdrop-filter: blur(2px);
|
||
backdrop-filter: blur(2px);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
padding: 20px;
|
||
}
|
||
.cta-modal-backdrop.is-open { display: flex; }
|
||
.cta-modal {
|
||
background: white;
|
||
border-radius: 14px;
|
||
padding: 28px 32px;
|
||
max-width: 520px;
|
||
width: 100%;
|
||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
|
||
color: #111827;
|
||
font-family: inherit;
|
||
}
|
||
.cta-modal h2 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0 0 6px;
|
||
color: #047857;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.cta-modal .lead {
|
||
font-size: 13px;
|
||
color: #6B7280;
|
||
margin: 0 0 18px;
|
||
line-height: 1.55;
|
||
}
|
||
.cta-modal ol {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0 0 18px;
|
||
counter-reset: step;
|
||
}
|
||
.cta-modal ol li {
|
||
counter-increment: step;
|
||
display: flex;
|
||
gap: 14px;
|
||
align-items: flex-start;
|
||
padding: 12px;
|
||
background: #F9FAFB;
|
||
border-radius: 8px;
|
||
margin-bottom: 10px;
|
||
font-size: 13.5px;
|
||
line-height: 1.5;
|
||
}
|
||
.cta-modal ol li::before {
|
||
content: counter(step);
|
||
width: 26px; height: 26px;
|
||
border-radius: 50%;
|
||
background: #0073D1;
|
||
color: white;
|
||
font-weight: 700;
|
||
font-size: 12px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.cta-modal ol li code,
|
||
.cta-modal ol li kbd {
|
||
background: #fff;
|
||
border: 1px solid #E5E7EB;
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono, ui-monospace, monospace);
|
||
font-size: 12px;
|
||
}
|
||
.cta-modal ol li kbd { border-bottom-width: 2px; }
|
||
.cta-modal ol li strong { display: block; margin-bottom: 4px; color: #111827; }
|
||
.cta-modal-foot {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
gap: 12px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid #F3F4F6;
|
||
}
|
||
.cta-modal-foot .meta { font-size: 12px; color: #6B7280; }
|
||
.cta-modal-foot button {
|
||
background: #0073D1;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 8px 18px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
}
|
||
.cta-modal-foot button:hover { background: #0056A3; }
|
||
</style>
|
||
|
||
<div class="home-mock">
|
||
|
||
{# Homepage status frame — five counters with 24h/7d toggle.
|
||
Two gates: (a) operator flag instance.home.show_status_frame /
|
||
AGNES_HOME_SHOW_STATUS_FRAME (default on, evaluated in router and
|
||
passed as `status_frame_enabled`); (b) the user being onboarded —
|
||
fresh users see a clean install-hero before zero-value stats.
|
||
Router skips `compute_home_stats` (saves the DB hit) when either
|
||
gate is closed, so `home_stats` is None in that branch. #}
|
||
{% if status_frame_enabled and onboarded and home_stats %}
|
||
{% include "_home_stats.html" %}
|
||
{% endif %}
|
||
|
||
{% set display_name = (user.name or (user.email or "").split("@")[0] or "there") %}
|
||
|
||
{# Install-hero renders only for not-onboarded users. Once `agnes init`
|
||
POSTs /api/me/onboarded (or the user clicks the in-hero X) the hero
|
||
disappears entirely — the rest of /home (connector tiles, news,
|
||
etc.) stays. Offboarding escape hatch moved to a discrete strip
|
||
below; see `.offboard-strip`. #}
|
||
{% if not onboarded %}
|
||
{# Getting Started renders FIRST in the not-onboarded flow as a
|
||
collapsed-by-default <details>. Click the summary to expand the
|
||
two-row map. First row anchors back to the install hero just
|
||
below (#install-hero); second row leaves the page for
|
||
/setup-advanced. Per-device dismiss X (data-dismiss-key) survives
|
||
on the generic .home-card-close handler — selector widened in JS
|
||
to accept <details> containers too. Disappears post-onboarding
|
||
alongside the hero so the in-page anchor never dangles. #}
|
||
<details class="home-getting-started" id="homeGettingStarted">
|
||
<summary class="home-gs-summary">
|
||
<span class="home-gs-summary-title">Getting Started</span>
|
||
<span class="home-gs-summary-hint">Two quick next steps — click to expand</span>
|
||
<span class="home-gs-summary-chev" aria-hidden="true">›</span>
|
||
</summary>
|
||
<button type="button" class="home-card-close"
|
||
data-dismiss-key="agnes_home_gs_dismissed"
|
||
aria-label="Dismiss Getting Started">×</button>
|
||
<a class="home-gs-item" href="#install-hero">
|
||
<span class="ico" aria-hidden="true">🚀</span>
|
||
<div class="text">
|
||
<strong>Setup {{ instance_brand }} in your Claude Code</strong>
|
||
<span class="desc">One-time install — walkthrough in the section below.</span>
|
||
</div>
|
||
<span class="arrow" aria-hidden="true">↓</span>
|
||
</a>
|
||
<a class="home-gs-item" href="/setup-advanced">
|
||
<span class="ico" aria-hidden="true">🛠️</span>
|
||
<div class="text">
|
||
<strong>Go deeper into your AI workspace</strong>
|
||
<span class="desc">VS Code layout, recommended plugins, multi-model second opinions, custom skills + rules + hooks, project workflows.</span>
|
||
</div>
|
||
<span class="arrow" aria-hidden="true">→</span>
|
||
</a>
|
||
</details>
|
||
|
||
<div class="install-hero" id="install-hero">
|
||
<button type="button" class="install-hero-close" id="installHeroClose"
|
||
data-target-source="self_acknowledged"
|
||
aria-label="I'm already set up — close this setup hero">×</button>
|
||
<div class="eyebrow">Welcome, {{ display_name }} — let's get you set up</div>
|
||
<h1>Connect Claude Code on your machine to your team's data</h1>
|
||
<p class="lead">
|
||
{{ instance_brand }} gives <strong>Claude Code</strong> on your computer access to your team's <strong>curated data, plugins, and shared knowledge</strong> — so you can ask questions and get answers in plain language, right from your terminal. This page walks you through the <strong>one-time setup (~10 minutes)</strong>. Everything it installs lives in your home folder (<code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">~/{{ workspace_dir }}</code>) and can be removed in one command.
|
||
</p>
|
||
|
||
<div class="install-block">
|
||
<div class="label">Step 1 — install Claude Code</div>
|
||
<div class="os-tabs" role="tablist" aria-label="Operating system">
|
||
<button type="button" role="tab" class="os-tab is-active"
|
||
data-os-tab="unix" aria-selected="true">macOS / Linux / WSL</button>
|
||
<button type="button" role="tab" class="os-tab"
|
||
data-os-tab="windows" aria-selected="false">Windows (PowerShell)</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="unix">
|
||
<span class="multiline" id="install-cmd-claude-unix">curl -fsSL https://claude.ai/install.sh | bash</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-claude-unix">Copy</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="windows" hidden>
|
||
<span class="multiline" id="install-cmd-claude-windows">irm https://claude.ai/install.ps1 | iex</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-claude-windows">Copy</button>
|
||
</div>
|
||
|
||
{# P0-1 — terminal-howto disclosure. Two panels (unix / windows)
|
||
that flip in lockstep with the install-cmd OS tabs above. #}
|
||
<details class="terminal-howto">
|
||
<summary>Don't have a terminal open? — show how</summary>
|
||
<div class="terminal-howto-body" data-howto-panel="unix">
|
||
<p><strong>macOS:</strong> press <kbd>⌘</kbd> + <kbd>Space</kbd>, type <kbd>Terminal</kbd>, press <kbd>Enter</kbd>.</p>
|
||
<p><strong>Linux:</strong> press <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>T</kbd> (most distros), or open the apps menu and search "Terminal".</p>
|
||
<p><strong>WSL:</strong> open the Windows Start menu, search "Ubuntu" (or your installed distro), press <kbd>Enter</kbd>.</p>
|
||
<p>Paste the command above with <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> (Linux/WSL) or <kbd>⌘</kbd> + <kbd>V</kbd> (macOS), then press <kbd>Enter</kbd>.</p>
|
||
</div>
|
||
<div class="terminal-howto-body" data-howto-panel="windows" hidden>
|
||
<p><strong>Windows:</strong> press <kbd>Win</kbd> + <kbd>R</kbd>, type <kbd>powershell</kbd>, press <kbd>Enter</kbd>. (Or: open Start menu, type "PowerShell".)</p>
|
||
<p>Paste the command above with <kbd>Ctrl</kbd> + <kbd>V</kbd> and press <kbd>Enter</kbd>.</p>
|
||
</div>
|
||
</details>
|
||
|
||
<div class="install-note">
|
||
Verify with <code>claude --version</code>. Sign in once with <code>claude</code> and complete the OAuth flow.
|
||
Don't have a Claude license yet? See <a href="/setup-advanced#claude-plan">plan options on /setup-advanced</a> (Pro / Max 5× / Max 20×).
|
||
</div>
|
||
</div>
|
||
|
||
{% if home_automode.show %}
|
||
<div class="install-block">
|
||
<div class="label">Step 2 — turn on auto-mode (recommended before Step 4)</div>
|
||
<div class="install-note">
|
||
In the Claude Code session you just signed into, press <strong>Shift + Tab</strong>. Claude cycles modes: default → <strong>auto-accept edits</strong> → plan mode → default; the footer shows <code>⏵⏵</code> when auto-accept is on. On the first cycle to auto-accept, Claude asks whether to make it the default — say <strong>yes</strong>. Closed the session already? Run <code>claude</code> again, then press <strong>Shift + Tab</strong>.
|
||
<br><br>
|
||
Want full auto-approve including Bash? See <a href="/setup-advanced#yolo">YOLO mode</a> on /setup-advanced — pairs <code>--dangerously-skip-permissions</code> with a reviewed <code>~/.claude/settings.local.json</code> allowlist. Skip if you're not sure.
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="install-block">
|
||
<div class="label">Step 3 — create your workspace folder</div>
|
||
<div class="os-tabs" role="tablist" aria-label="Operating system">
|
||
<button type="button" role="tab" class="os-tab is-active"
|
||
data-os-tab="unix" aria-selected="true">macOS / Linux / WSL</button>
|
||
<button type="button" role="tab" class="os-tab"
|
||
data-os-tab="windows" aria-selected="false">Windows (PowerShell)</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="unix">
|
||
<span class="multiline" id="install-cmd-mkdir-unix">mkdir -p ~/{{ workspace_dir }} && cd ~/{{ workspace_dir }}</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-mkdir-unix">Copy</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="windows" hidden>
|
||
<span class="multiline" id="install-cmd-mkdir-windows">New-Item -ItemType Directory -Force -Path "$HOME\{{ workspace_dir }}" | Out-Null
|
||
Set-Location "$HOME\{{ workspace_dir }}"</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-mkdir-windows">Copy</button>
|
||
</div>
|
||
<div class="install-note">
|
||
This is where {{ instance_brand }} will live. Run the command in the terminal you opened for Step 1, then keep that terminal handy — the next step pastes the setup script into Claude Code from this same directory.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="install-block">
|
||
<div class="label">Step 4 — install {{ instance_brand }} from inside Claude Code</div>
|
||
<p class="setup-cta-lead">
|
||
Click the button — {{ instance_brand }} <strong>creates a 90-day login token</strong>, copies a ready-to-paste setup script to your clipboard, and a follow-up popup tells you exactly where to paste it. The script bootstraps everything in <code>~/{{ workspace_dir }}</code> once Claude Code receives it.
|
||
</p>
|
||
<div class="setup-cta-row">
|
||
<button type="button" id="setupClaudeBtn" class="btn-setup-primary" onclick="setupNewClaude(this)">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||
</svg>
|
||
Setup a new Claude Code
|
||
</button>
|
||
<span class="setup-cta-meta">
|
||
<span class="setup-cta-hint">Valid 90 days · token stays in clipboard only</span>
|
||
</span>
|
||
</div>
|
||
<div id="setupClaudeError" class="setup-error" role="alert" style="display:none;"></div>
|
||
|
||
<details class="manual-fallback">
|
||
<summary>Or paste manually (preview the script)</summary>
|
||
<div class="manual-preview-wrap">
|
||
{% with preview_mode=True %}
|
||
{% include "_claude_setup_instructions.jinja" %}
|
||
{% endwith %}
|
||
</div>
|
||
<div class="install-note">
|
||
The preview above is the exact text the button copies; the placeholder is replaced with a real token at click time. Don't create <code>~/{{ workspace_dir }}/Projects/</code> manually — the bundled plugin offers to set it up after install.
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
|
||
{# P1-6 — auto-detect badge is the PRIMARY affordance after the
|
||
install-script copy: agnes-init's first POST to
|
||
/api/me/onboarded flips state automatically and the page
|
||
reloads. The manual "Mark me as onboarded" button below it
|
||
stays as a fallback when auto-flip never lands. #}
|
||
<div class="auto-detect-badge" role="status" aria-live="polite">
|
||
<span class="pulse" aria-hidden="true"></span>
|
||
<span>Waiting for your first <code>agnes pull</code> — auto-detects within ~30 s of the setup script finishing.</span>
|
||
</div>
|
||
|
||
{# Self-mark fallback for the auto-flip. The hero's X close button
|
||
does the same thing more visibly; both target the not-onboarded
|
||
→ onboarded direction. The onboarded → offboarded variant lives
|
||
below the hero (.offboard-strip) so it stays reachable once the
|
||
hero is gone. #}
|
||
<div class="self-mark">
|
||
Already set this up?
|
||
<button id="self-mark-btn" type="button"
|
||
data-target-onboarded="true"
|
||
data-target-source="self_acknowledged">Mark me as onboarded</button>
|
||
<span id="self-mark-status" class="status" role="status" aria-live="polite"></span>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if onboarded %}
|
||
{# Offboarding escape hatch shown only after the hero has disappeared.
|
||
Lets the analyst (e.g. after wiping ~/FoundryAI) flip the
|
||
users.onboarded boolean back to false so the full install hero
|
||
renders again on next reload. Discrete by design — onboarded
|
||
users land on /home expecting the nav hub, not a setup screen. #}
|
||
<div class="offboard-strip">
|
||
<span>Workspace ready — wiped it and need the full setup view back?</span>
|
||
<button id="offboard-btn" type="button"
|
||
data-target-source="self_unmark">Mark me as offboarded</button>
|
||
<span id="offboard-status" class="status" role="status" aria-live="polite"></span>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Auto-mode card used to live here as a `<details>` reference block;
|
||
moved into the install-hero as the new Step 2 so users enable it
|
||
BEFORE Step 3's install runs ~20 commands. Gated by the same
|
||
`home_automode.show` flag at the call site. #}
|
||
|
||
{# Getting Started was previously rendered HERE (between the offboard
|
||
strip and Overview) as a full-card <section>. Moved up to render
|
||
BEFORE the install-hero as a collapsed-by-default <details> — see
|
||
the block right after `{% if not onboarded %}` near line ~1357. #}
|
||
|
||
{# Overview section — operator-owned, opt-in. Body comes from the
|
||
`instance.overview` yaml field via get_instance_overview()
|
||
(`AGNES_INSTANCE_OVERVIEW` env override). Empty value hides the
|
||
whole section, keeping the OSS vendor-neutral. #}
|
||
{# Overview is operator-owned reference content, not per-user
|
||
chrome — no dismiss button on purpose. A one-time per-device
|
||
hide would mean returning users who wanted to re-read the
|
||
privacy posture / telemetry policy can't get back to it
|
||
without clearing localStorage. The whole section is opt-in at
|
||
the operator level (empty yaml → section absent), which is
|
||
the right axis of control. #}
|
||
{% if config.INSTANCE_OVERVIEW %}
|
||
<section class="home-overview">
|
||
<h2>Overview</h2>
|
||
<div class="home-overview-body">{{ config.INSTANCE_OVERVIEW | safe }}</div>
|
||
</section>
|
||
{% endif %}
|
||
|
||
{# Usage modes — Terminal / VS Code / Claude Desktop · claude.ai.
|
||
Generic OSS-shipped copy linking to /setup and to /setup-advanced
|
||
anchors. Helps onboarded users find the right surface once they
|
||
know which workflow fits them. #}
|
||
<section class="home-usage">
|
||
<header>
|
||
<h2>Where you can use {{ instance_brand }}</h2>
|
||
<p>Same workspace, three surfaces. Pick whichever fits your flow — all three share the same plugins, data access, and credentials.</p>
|
||
</header>
|
||
<div class="home-usage-grid">
|
||
{# Terminal tile is informational — the setup hero above already
|
||
walks through the Claude Code CLI install, so a click-through
|
||
here would round-trip back to content the user just scrolled
|
||
past. Rendered as a non-anchor div; hover/cursor styles only
|
||
apply to the anchor variants below. #}
|
||
<div class="home-usage-item">
|
||
<span class="ico" aria-hidden="true">⌨️</span>
|
||
<strong>Terminal</strong>
|
||
<span>Default. Run <code>claude</code> from any project under <code>~/{{ workspace_dir }}</code>. Lowest overhead, fastest feedback loop.</span>
|
||
</div>
|
||
<a class="home-usage-item" href="/setup-advanced#vscode">
|
||
<span class="ico" aria-hidden="true">📑</span>
|
||
<strong>VS Code</strong>
|
||
<span>Split-terminal layout — conversation on one side, diffs and tool output on the other. See <em>Go deeper</em> for the recommended layout.</span>
|
||
</a>
|
||
<a class="home-usage-item" href="/setup-advanced#claude-app">
|
||
<span class="ico" aria-hidden="true">💻</span>
|
||
<strong>Claude Desktop / claude.ai</strong>
|
||
<span>Connect this {{ instance_brand }} instance's plugin marketplace to your Claude Desktop or claude.ai account — same plugins, no terminal required.</span>
|
||
</a>
|
||
</div>
|
||
<p class="home-usage-foot">For the deepest integration, create every project under <code>~/{{ workspace_dir }}/Projects/</code> — existing or new. The bundled plugin keeps each project in sync with the central catalog automatically, and the session-analysis loop is scoped to that root.</p>
|
||
</section>
|
||
|
||
<details class="setup-collapsible" data-section="connectors" open>
|
||
<summary>
|
||
<span class="ico" aria-hidden="true">🔗</span>
|
||
<span class="ttl">Connect your tools <small>(Asana / Google Workspace / Atlassian)</small></span>
|
||
<span class="chev" aria-hidden="true">›</span>
|
||
</summary>
|
||
<div class="section-label section-label-flat">Once {{ instance_brand }} is installed — connect your tools</div>
|
||
|
||
<div class="connector-tiles">
|
||
<div class="connector-tile">
|
||
<span class="ico">✅</span>
|
||
{# P0-3 — title row + time-badge + post-copy hint. The hint
|
||
container is rendered hidden; the JS reveals it after the
|
||
copy succeeds, then auto-hides after 8 s. #}
|
||
<div class="ttl-row">
|
||
<span class="ttl">Asana</span>
|
||
<span class="time-badge">~5 min · self-serve</span>
|
||
</div>
|
||
<div class="desc">Read tasks and projects, comment, create updates — Claude works alongside your project boards without leaving the terminal.</div>
|
||
<div class="connector-actions">
|
||
<button class="connector-copy" data-copy-target="asana-prompt" data-connector="Asana">Copy prompt</button>
|
||
<div class="copy-next-hint" data-hint-for="Asana">
|
||
<span>✅ Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste & press Enter.</span>
|
||
</div>
|
||
<details class="connector-preview">
|
||
<summary>Show prompt</summary>
|
||
{# Asana prompt body sourced from app/web/connector_prompts.py
|
||
(`asana_prompt()`). Same string the main setup script
|
||
inlines in step 9, so the two surfaces stay in lockstep. #}
|
||
<div class="card-mini-cmd"><code id="asana-prompt">{{ connector_prompts.asana }}</code></div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="connector-tile">
|
||
<span class="ico">📚</span>
|
||
{# P0-3 + P1-8 — Google Workspace tile. Time badge warns when
|
||
the operator hasn't provisioned a shared OAuth app (forces
|
||
the user to set up GCP themselves, which is a ~20-min
|
||
clickops detour). The gating-note + email-admin button
|
||
appear in that same un-configured branch so the user has
|
||
a way out before copying. #}
|
||
<div class="ttl-row">
|
||
<span class="ttl">Google Workspace</span>
|
||
{% if gws_oauth.configured %}
|
||
<span class="time-badge">~5 min · self-serve</span>
|
||
{% else %}
|
||
<span class="time-badge is-warn">~20 min · admin help likely</span>
|
||
{% endif %}
|
||
</div>
|
||
<div class="desc">Drive, Calendar, Gmail, Docs, Sheets, Chat — Claude reads and acts across your work account via the official <code>gws</code> CLI.</div>
|
||
{% if not gws_oauth.configured %}
|
||
<div class="gating-note">
|
||
<strong>Heads up:</strong> your {{ instance_brand }} admin hasn't provisioned a shared Google Cloud OAuth app yet, so this connector needs GCP project setup (creating an OAuth client, enabling APIs). It's fastest to ask your admin first — the button below pre-fills the email.
|
||
</div>
|
||
{% endif %}
|
||
<div class="connector-actions">
|
||
<button class="connector-copy" data-copy-target="gws-prompt" data-connector="Google Workspace">Copy prompt</button>
|
||
{% if not gws_oauth.configured and instance_admin_email %}
|
||
<a class="email-admin" href="mailto:{{ instance_admin_email }}?subject={{ instance_brand|urlencode }}%20%E2%80%94%20Google%20Workspace%20OAuth%20setup&body=Hi%20%E2%80%94%20I'd%20like%20to%20connect%20Google%20Workspace%20in%20{{ instance_brand|urlencode }}%20but%20it%20looks%20like%20a%20shared%20OAuth%20app%20isn't%20provisioned%20yet.%20Could%20you%20set%20it%20up%20for%20our%20instance%3F%20Thanks!">
|
||
✉️ Email admin
|
||
</a>
|
||
{% endif %}
|
||
<div class="copy-next-hint" data-hint-for="Google Workspace">
|
||
<span>✅ Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste & press Enter.</span>
|
||
</div>
|
||
<details class="connector-preview">
|
||
<summary>Show prompt</summary>
|
||
<div class="card-mini-cmd"><code id="gws-prompt">{{ connector_prompts.gws }}</code></div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="connector-tile">
|
||
<span class="ico">🎟️</span>
|
||
<div class="ttl-row">
|
||
<span class="ttl">Atlassian (Jira / Confluence)</span>
|
||
<span class="time-badge">~7 min · self-serve</span>
|
||
</div>
|
||
<div class="desc">Read and write Jira issues, search Confluence pages — Claude pulls ticket context and posts updates without leaving the workspace.</div>
|
||
<div class="connector-actions">
|
||
<button class="connector-copy" data-copy-target="jira-prompt" data-connector="Atlassian">Copy prompt</button>
|
||
<div class="copy-next-hint" data-hint-for="Atlassian">
|
||
<span>✅ Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste & press Enter.</span>
|
||
</div>
|
||
<details class="connector-preview">
|
||
<summary>Show prompt</summary>
|
||
<div class="card-mini-cmd"><code id="jira-prompt">{{ connector_prompts.atlassian }}</code></div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<div class="section-label">Want to look around first?</div>
|
||
<p class="look-around-lead">You don't need {{ instance_brand }} installed locally to browse what's available. Anything you bookmark or subscribe to will be there waiting after you set {{ instance_brand }} up.</p>
|
||
|
||
<div class="what-is look-around-grid">
|
||
<a class="what-is-item" href="/marketplace">
|
||
<span class="ico">🧩</span>
|
||
<div class="ttl">Plugin marketplace <span class="arrow">→</span></div>
|
||
<div class="desc">Curated and community-built plugins for Claude Code — Asana, Jira, Google Workspace, and more. Browse, install, sync.</div>
|
||
</a>
|
||
<a class="what-is-item" href="/catalog">
|
||
<span class="ico">📦</span>
|
||
<div class="ttl">Curated data packages <span class="arrow">→</span></div>
|
||
<div class="desc">Tables, schema, and metric definitions your team has registered. Subscribe and Claude can query them with documented business rules.</div>
|
||
</a>
|
||
{# Corporate memory + Activity center are both admin-only — matches
|
||
the top-nav gating logic (the "Memory" link is hidden for
|
||
non-admin in _app_header.html, and the /corporate-memory route
|
||
is `require_admin`). Without this gate non-admin users would
|
||
see a Corporate memory tile that 403s when clicked. #}
|
||
{% if is_admin %}
|
||
<a class="what-is-item" href="/corporate-memory">
|
||
<span class="ico">🧠</span>
|
||
<div class="ttl">Corporate memory <span class="arrow">→</span></div>
|
||
<div class="desc">Shared analyst knowledge and prior solutions to draw from. Searchable, versioned, fed back into Claude's context on demand.</div>
|
||
</a>
|
||
<a class="what-is-item" href="/activity-center">
|
||
<span class="ico">📈</span>
|
||
<div class="ttl">Activity center <span class="arrow">→</span></div>
|
||
<div class="desc">Per-user analytics on {{ instance_brand }} adoption across your team. Sessions, plugin installs, prompt patterns.</div>
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Legacy `.advanced-pointer` row removed — same link now lives in
|
||
the Getting Started card at the top of /home. `.advanced-pointer`
|
||
CSS (~line 721) is harmless dead style; left in place to keep
|
||
this diff focused. #}
|
||
|
||
{% if news_intro %}
|
||
<section class="home-news">
|
||
<header class="home-news-head">
|
||
<h2>What's new</h2>
|
||
<a class="home-news-more" href="/news">Read more →</a>
|
||
</header>
|
||
<div class="home-news-body">{{ news_intro | safe }}</div>
|
||
</section>
|
||
{% endif %}
|
||
|
||
</div>
|
||
|
||
{# P0-2 — Post-CTA modal. Opens after the shared CTA include below has
|
||
created the token + copied the script. _claude_setup_cta.jinja still
|
||
owns the token request + clipboard write; this modal layers on top
|
||
with a 3-step "where to paste" guide. Lives at body level so the
|
||
home-mock styles don't bleed into it. #}
|
||
{% if not onboarded %}
|
||
<div class="cta-modal-backdrop" id="cta-modal-backdrop" role="dialog" aria-modal="true" aria-labelledby="cta-modal-title" hidden>
|
||
<div class="cta-modal">
|
||
<h2 id="cta-modal-title"><span aria-hidden="true">✅</span> Setup script copied to clipboard</h2>
|
||
<p class="lead">Now paste it into Claude Code on your machine — three steps:</p>
|
||
<ol>
|
||
<li>
|
||
<div>
|
||
<strong>Open a terminal</strong>
|
||
macOS: <kbd>⌘</kbd>+<kbd>Space</kbd>, type "Terminal". Windows: <kbd>Win</kbd>+<kbd>R</kbd>, type <code>powershell</code>. Linux: <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>T</kbd>.
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<div>
|
||
<strong>Start Claude Code</strong>
|
||
Type <code>claude</code> and press <kbd>Enter</kbd>. You'll see a prompt waiting for input.
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<div>
|
||
<strong>Paste & press Enter</strong>
|
||
macOS: <kbd>⌘</kbd>+<kbd>V</kbd>. Windows/Linux: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>V</kbd>. Claude runs the setup and tells you when it's done.
|
||
</div>
|
||
</li>
|
||
</ol>
|
||
<div class="cta-modal-foot">
|
||
<span class="meta">Token is in clipboard only — never displayed here.</span>
|
||
<button type="button" id="cta-modal-close">Got it</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Shared "Setup a new Claude Code" CTA behaviour — provides the JS that
|
||
POSTs /auth/tokens, copies the rendered instructions to the clipboard,
|
||
and falls back to a modal when the clipboard API is blocked. The button
|
||
above (id="setupClaudeBtn") is the primary trigger. #}
|
||
{% include "_claude_setup_cta.jinja" %}
|
||
|
||
<script>
|
||
(function () {
|
||
function wireCopy(btn) {
|
||
btn.addEventListener('click', function () {
|
||
var src = document.getElementById(btn.getAttribute('data-copy-target'));
|
||
if (!src) return;
|
||
// textContent (not innerText) so collapsed <details> content still
|
||
// copies — innerText returns "" for nodes whose ancestor has
|
||
// display:none (which is what closed <details> applies).
|
||
var raw = src.textContent || '';
|
||
var text = raw.replace(/ /g, ' ');
|
||
navigator.clipboard.writeText(text).then(function () {
|
||
var orig = btn.textContent;
|
||
btn.textContent = 'Copied';
|
||
setTimeout(function () { btn.textContent = orig; }, 1500);
|
||
}).catch(function () { btn.textContent = 'Copy failed'; });
|
||
});
|
||
}
|
||
document.querySelectorAll('.copy-btn[data-copy-target]').forEach(wireCopy);
|
||
|
||
// P0-3 + P2-12 — Connector copy buttons. Three things on click:
|
||
// 1) copy the prompt (same textContent trick so closed <details>
|
||
// still copies);
|
||
// 2) flip the button to "✓ Copied" + disabled for 8 s so the user
|
||
// stops mashing it while figuring out where to paste;
|
||
// 3) reveal a paste hint pointing the user at Claude Code.
|
||
document.querySelectorAll('.connector-copy[data-copy-target]').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
var src = document.getElementById(btn.getAttribute('data-copy-target'));
|
||
if (!src) return;
|
||
var raw = src.textContent || '';
|
||
var connector = btn.getAttribute('data-connector') || '';
|
||
var hint = connector
|
||
? document.querySelector('.copy-next-hint[data-hint-for="' + connector + '"]')
|
||
: null;
|
||
navigator.clipboard.writeText(raw).then(function () {
|
||
var orig = btn.textContent;
|
||
btn.classList.add('copied');
|
||
btn.textContent = '✓ Copied';
|
||
btn.disabled = true;
|
||
if (hint) hint.classList.add('is-visible');
|
||
setTimeout(function () {
|
||
btn.classList.remove('copied');
|
||
btn.textContent = orig;
|
||
btn.disabled = false;
|
||
if (hint) hint.classList.remove('is-visible');
|
||
}, 8000);
|
||
}).catch(function () { btn.textContent = 'Copy failed'; });
|
||
});
|
||
});
|
||
|
||
// OS tab switching for Step 1. Flips both the command panel AND the
|
||
// P0-1 terminal-howto body so the howto matches the active OS.
|
||
document.querySelectorAll('.os-tab[data-os-tab]').forEach(function (tab) {
|
||
tab.addEventListener('click', function () {
|
||
var target = tab.getAttribute('data-os-tab');
|
||
var scope = tab.closest('.install-block') || document;
|
||
scope.querySelectorAll('.os-tab[data-os-tab]').forEach(function (t) {
|
||
var on = t.getAttribute('data-os-tab') === target;
|
||
t.classList.toggle('is-active', on);
|
||
t.setAttribute('aria-selected', on ? 'true' : 'false');
|
||
});
|
||
scope.querySelectorAll('[data-os-panel]').forEach(function (p) {
|
||
if (p.getAttribute('data-os-panel') === target) {
|
||
p.removeAttribute('hidden');
|
||
} else {
|
||
p.setAttribute('hidden', '');
|
||
}
|
||
});
|
||
// P0-1 — flip the howto bodies in lockstep.
|
||
scope.querySelectorAll('[data-howto-panel]').forEach(function (p) {
|
||
if (p.getAttribute('data-howto-panel') === target) {
|
||
p.removeAttribute('hidden');
|
||
} else {
|
||
p.setAttribute('hidden', '');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// P0-2 — Post-CTA modal. _claude_setup_cta.jinja owns the click
|
||
// handler that POSTs /auth/tokens + copies the script; we wait for
|
||
// its success signal (agnes:setup-script-copied custom event) and
|
||
// then open the "where to paste" guide. Without the event the modal
|
||
// simply never opens — the include's own fallback paths handle
|
||
// older browsers / blocked clipboard.
|
||
var ctaModal = document.getElementById('cta-modal-backdrop');
|
||
var ctaModalClose = document.getElementById('cta-modal-close');
|
||
function openCtaModal() {
|
||
if (!ctaModal) return;
|
||
ctaModal.removeAttribute('hidden');
|
||
ctaModal.classList.add('is-open');
|
||
if (ctaModalClose) ctaModalClose.focus();
|
||
}
|
||
function closeCtaModal() {
|
||
if (!ctaModal) return;
|
||
ctaModal.classList.remove('is-open');
|
||
ctaModal.setAttribute('hidden', '');
|
||
var setupBtn = document.getElementById('setupClaudeBtn');
|
||
if (setupBtn) setupBtn.focus();
|
||
}
|
||
if (ctaModal) {
|
||
document.addEventListener('agnes:setup-script-copied', openCtaModal);
|
||
if (ctaModalClose) ctaModalClose.addEventListener('click', closeCtaModal);
|
||
ctaModal.addEventListener('click', function (ev) {
|
||
if (ev.target === ctaModal) closeCtaModal();
|
||
});
|
||
document.addEventListener('keydown', function (ev) {
|
||
if (ev.key === 'Escape' && ctaModal.classList.contains('is-open')) closeCtaModal();
|
||
});
|
||
}
|
||
|
||
// Shared poster for /api/me/onboarded — reused by every UI surface
|
||
// that flips users.onboarded (in-hero X close, "Mark me as onboarded"
|
||
// fallback button, the offboard strip). Reloads on success so the
|
||
// template re-renders with the new state.
|
||
function postOnboarded(triggerBtn, statusEl, targetOnboarded, targetSource) {
|
||
if (triggerBtn) triggerBtn.disabled = true;
|
||
if (statusEl) statusEl.textContent = targetOnboarded ? 'Marking…' : 'Resetting…';
|
||
fetch('/api/me/onboarded', {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ source: targetSource, onboarded: targetOnboarded }),
|
||
}).then(function (resp) {
|
||
if (resp.ok) {
|
||
if (statusEl) statusEl.textContent = 'Done. Reloading…';
|
||
window.location.reload();
|
||
} else {
|
||
if (statusEl) statusEl.textContent = 'Failed (' + resp.status + '). Try again.';
|
||
if (triggerBtn) triggerBtn.disabled = false;
|
||
}
|
||
}).catch(function () {
|
||
if (statusEl) statusEl.textContent = 'Network error. Try again.';
|
||
if (triggerBtn) triggerBtn.disabled = false;
|
||
});
|
||
}
|
||
|
||
// "Mark me as onboarded" fallback button inside the hero (rendered
|
||
// only when not-onboarded — the X close button is the primary path).
|
||
var btn = document.getElementById('self-mark-btn');
|
||
var status = document.getElementById('self-mark-status');
|
||
if (btn) {
|
||
btn.addEventListener('click', function () {
|
||
postOnboarded(
|
||
btn, status, true,
|
||
btn.getAttribute('data-target-source') || 'self_acknowledged'
|
||
);
|
||
});
|
||
}
|
||
|
||
// Hero X close — confirm first so a stray click doesn't flip state.
|
||
var heroClose = document.getElementById('installHeroClose');
|
||
if (heroClose) {
|
||
heroClose.addEventListener('click', function () {
|
||
var ok = window.confirm(
|
||
"Are you already onboarded? Closing this will mark your account as onboarded " +
|
||
"and hide the setup steps. You can revert later from the strip below the hero."
|
||
);
|
||
if (!ok) return;
|
||
postOnboarded(
|
||
heroClose, status, true,
|
||
heroClose.getAttribute('data-target-source') || 'self_acknowledged'
|
||
);
|
||
});
|
||
}
|
||
|
||
// Offboarding strip — only rendered when onboarded. Flips back to
|
||
// the install hero on next reload.
|
||
var offBtn = document.getElementById('offboard-btn');
|
||
var offStatus = document.getElementById('offboard-status');
|
||
if (offBtn) {
|
||
offBtn.addEventListener('click', function () {
|
||
postOnboarded(
|
||
offBtn, offStatus, false,
|
||
offBtn.getAttribute('data-target-source') || 'self_unmark'
|
||
);
|
||
});
|
||
}
|
||
|
||
// ── Minimize-setup toggle ────────────────────────────────────────
|
||
// Default OFF: sections render flat (no <summary> visible).
|
||
// ON: data-setup-minimized="1" on .home-mock activates the slim
|
||
// gray summary bars; <details> open/close handles per-section
|
||
// expansion. Per-device via localStorage; survives reloads but
|
||
// resets on a new machine — which is correct (the user might
|
||
// want the full reference there).
|
||
var KEY = 'agnes_home_setup_minimized';
|
||
var mockEl = document.querySelector('.home-mock');
|
||
var minToggle = document.getElementById('setupMinimizeToggle');
|
||
var collapsibles = document.querySelectorAll('.setup-collapsible');
|
||
|
||
function applyMinimize(on) {
|
||
if (!mockEl) return;
|
||
if (on) {
|
||
mockEl.setAttribute('data-setup-minimized', '1');
|
||
collapsibles.forEach(function (d) { d.removeAttribute('open'); });
|
||
if (minToggle) {
|
||
minToggle.textContent = 'Show full setup view';
|
||
minToggle.setAttribute('aria-pressed', 'true');
|
||
}
|
||
} else {
|
||
mockEl.removeAttribute('data-setup-minimized');
|
||
collapsibles.forEach(function (d) { d.setAttribute('open', ''); });
|
||
if (minToggle) {
|
||
minToggle.textContent = 'Minimize setup view';
|
||
minToggle.setAttribute('aria-pressed', 'false');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Initial state — only when the toggle exists (= onboarded view).
|
||
if (minToggle) {
|
||
try { applyMinimize(localStorage.getItem(KEY) === '1'); }
|
||
catch (e) { /* ignore localStorage failures (private mode) */ }
|
||
minToggle.addEventListener('click', function () {
|
||
var nowOn = mockEl.getAttribute('data-setup-minimized') !== '1';
|
||
try { localStorage.setItem(KEY, nowOn ? '1' : '0'); } catch (e) {}
|
||
applyMinimize(nowOn);
|
||
});
|
||
}
|
||
|
||
// ── Generic dismiss-card handler ─────────────────────────────────
|
||
// Any `<button class="home-card-close" data-dismiss-key="...">`
|
||
// inside a `<section>` becomes dismissible: clicking sets the
|
||
// localStorage key to '1' and hides the section; on next page
|
||
// load the section starts hidden if the key is set. Used by the
|
||
// Getting Started card; future dismissible cards drop in with
|
||
// zero per-card JS.
|
||
document.querySelectorAll('.home-card-close[data-dismiss-key]').forEach(function (btn) {
|
||
var key = btn.getAttribute('data-dismiss-key');
|
||
var section = btn.closest('section, details');
|
||
if (!section || !key) return;
|
||
try {
|
||
if (localStorage.getItem(key) === '1') {
|
||
section.hidden = true;
|
||
return;
|
||
}
|
||
} catch (e) { /* private-mode: render visible, no-op */ }
|
||
btn.addEventListener('click', function () {
|
||
try { localStorage.setItem(key, '1'); } catch (e) { /* ignore */ }
|
||
section.hidden = true;
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
{% endblock %}
|