agnes-the-ai-analyst/app/web/templates/home_not_onboarded.html
Vojtech 1e87354d7e
feat(home): Getting Started + Overview + Usage modes sections (release 0.54.7) (#291)
* feat(home): Getting Started + Overview + Usage modes sections

Three new content cards rendered between the install-hero and the
existing connector tiles on /home. Order: Getting Started → Overview
→ Usage modes → connectors.

- Getting Started — dismissible card with two clickable rows linking
  to /setup (install flow) and /setup-advanced (deeper reference).
  Subsumes the legacy `.advanced-pointer` row that sat above the news
  section. Per-device dismiss via a generic localStorage handler:
  `.home-card-close[data-dismiss-key="..."]` inside a <section> wires
  itself up at page load — drop in any future dismissible card without
  per-card JS.
- Overview — operator-owned HTML body sourced from the new
  `instance.overview` yaml field (env override
  `AGNES_INSTANCE_OVERVIEW`). HTML in, HTML out via the same `| safe`
  filter as news_intro. Empty default hides the section entirely,
  keeping the OSS vendor-neutral; operators paste their product
  framing / privacy posture into instance.yaml. New helper
  `get_instance_overview()` in app/instance_config.py mirrors
  `get_instance_logo_svg()`.
- Usage modes — three OSS-shipped tiles (Terminal / VS Code / Claude
  Desktop · claude.ai) explaining each surface and linking to the
  matching /setup-advanced anchors. Closes the gap for users
  wondering "where do I actually run this".

Supporting changes:
- setup_advanced.html gains a new `#claude-app` section between
  #vscode and #workspace, anchored by the Usage modes Claude Desktop
  tile. Covers the marketplace registration paths and when to prefer
  the terminal. Added to the table of contents.
- Three new tests in test_web_home_page.py pin the Getting Started
  card markup, the Overview-on-when-yaml-set path, and the
  Overview-off-by-default path. All 13 tests in the file pass.

Operator follow-up (separate infra PR — NOT this PR): paste the
Foundry-specific Overview body into instance.yaml's
`instance.overview` field. OSS ships with an empty default.

* fix(home): Overview is operator-owned content — drop dismiss button

Earlier iteration added a close X to the Overview section to match
the Getting Started card's dismiss UX. Wrong call: Overview is
operator-authored reference content (privacy posture, telemetry
policy, project framing) and a per-device localStorage hide means
returning users who want to re-read the policy can't recover it
without clearing storage.

Reverts the close button + the data-dismiss-key on the Overview
section. Test inverted to assert the dismiss key is absent (defends
against a future drive-by adding it back). Getting Started still
dismisses — that's procedural getting-started content users
legitimately stop needing once they've finished setup. Overview is
always reachable; whole section is still opt-in at the operator
level via the empty-yaml default.

* fix(home): Terminal usage-mode tile is informational (no click-through)

The setup hero above /home's Usage modes already walks the user
through the Claude Code CLI install — the Terminal tile click-through
to /setup just round-trips back to content the user already scrolled
past. Switch Terminal to a non-anchor <div> and scope the hover
affordance to a.home-usage-item so VS Code + Claude Desktop tiles
keep their click-through (those legitimately deep-link into
/setup-advanced anchors).

* fix(home): point Usage modes guidance at ~/{workspace}/Projects/ subfolder

The bundled plugin scopes the session-analysis loop and the
central-catalog sync to ~/<workspace>/Projects/, not the workspace
root itself — that convention already appears in the install hero's
Step 4 manual-fallback note ('Don't create ~/<workspace>/Projects/
manually — the bundled plugin offers to set it up after install').
Usage modes' footer guidance now matches: 'create every project
under ~/<workspace>/Projects/'. Also calls out that the
session-analysis loop is scoped to that root so users understand
why working outside the workspace dir is invisible to the platform.
2026-05-13 21:44:11 +02:00

2033 lines
76 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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);
}
.home-mock .home-getting-started > header h2,
.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-getting-started > header p,
.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">
{% 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 %}
<div class="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">&times;</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 }} &amp;&amp; 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 card — dismissible per-device via localStorage.
Two clickable rows pointing at the install flow (/setup) and the
deeper reference (/setup-advanced). Subsumes the legacy
`.advanced-pointer` row that used to sit above the news section. #}
<section class="home-getting-started" id="homeGettingStarted">
<button type="button" class="home-card-close"
data-dismiss-key="agnes_home_gs_dismissed"
aria-label="Dismiss Getting Started">&times;</button>
<header>
<h2>Getting Started</h2>
<p>Two quick next steps to get the most out of {{ instance_brand }}.</p>
</header>
<a class="home-gs-item" href="/setup">
<span class="ico" aria-hidden="true">&#x1F680;</span>
<div class="text">
<strong>Setup {{ instance_brand }} in your Claude Code</strong>
<span class="desc">One-time install: copies a setup script to your clipboard, paste into Claude Code, done in ~10 minutes.</span>
</div>
<span class="arrow" aria-hidden="true">&rarr;</span>
</a>
<a class="home-gs-item" href="/setup-advanced">
<span class="ico" aria-hidden="true">&#x1F6E0;&#xFE0F;</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">&rarr;</span>
</a>
</section>
{# 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">&#x2328;&#xFE0F;</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">&#x1F4D1;</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">&#x1F4BB;</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">&#x1F517;</span>
<span class="ttl">Connect your tools <small>(Asana / Google Workspace / Atlassian)</small></span>
<span class="chev" aria-hidden="true">&rsaquo;</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">&#x2705;</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>&#x2705; Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste &amp; 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">&#x1F4DA;</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&amp;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!">
&#x2709;&#xFE0F; Email admin
</a>
{% endif %}
<div class="copy-next-hint" data-hint-for="Google Workspace">
<span>&#x2705; Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste &amp; 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">&#x1F39F;&#xFE0F;</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>&#x2705; Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste &amp; 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">&#x1F9E9;</span>
<div class="ttl">Plugin marketplace <span class="arrow">&rarr;</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">&#x1F4E6;</span>
<div class="ttl">Curated data packages <span class="arrow">&rarr;</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">&#x1F9E0;</span>
<div class="ttl">Corporate memory <span class="arrow">&rarr;</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">&#x1F4C8;</span>
<div class="ttl">Activity center <span class="arrow">&rarr;</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 &rarr;</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">&#x2705;</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 &amp; 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');
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 %}