# Circa — people first, context always

Build a complete, original relationship-focused CRM concept named Circa. The slug was checked against the existing source and generated-demo directories and is unused. The product should connect fictional people, companies, and opportunities through a precise white, ink, and cobalt interface. It needs a substantial marketing page, people and company workspaces, linked record details, a working deal pipeline, local notes, saved filter views, safe CSV and JSON exports, and a clear guide. It must look and behave differently from a warm editorial journal, an analytics dashboard, or a support-message inbox.

The central idea is that context belongs near the relationship it describes. A person belongs to a company, an opportunity connects to both, and a note has a clear record. The visitor can follow those connections and change selected local data without losing the larger picture. Keep the scope honest: no inbox connection, external CRM, data enrichment, automatic outreach, artificial-intelligence analysis, or real commercial activity occurs. All source records and amounts are fictional demonstration content.

## Live reference research

The live reference is Attio at https://attio.com/. The homepage was opened and inspected at a desktop viewport of 1471 by 847. Its current presentation uses a restrained white and ink hero, compact navigation, a large centered proposition, paired actions, and a broad product showcase below. The visible hero heading was measured at sixty-four pixels in Inter Display, weight six hundred, with a 60.8-pixel line height. Its rendered text area was approximately 804.6 pixels wide and 60.8 pixels high, beginning around x=333 and y=278.

The reference’s lower product sections connect people, account context, and deal actions. A sticky feature navigation sits beside substantial product demonstrations, and the content repeatedly returns to the relationship between a person, an organization, and the work around them. That is the useful structural principle for Circa. The marketing page should introduce a connected record model that the actual demo can then demonstrate, rather than advertising a complex system and offering only a static picture.

The current reference makes claims about agents, automated enrichment, outreach, and revenue operations. Circa must not repeat those claims. Its local interactions are explicit and manual: filtering records, saving a view, writing a note, editing a deal stage or amount, and exporting data. Search is ordinary text matching. Totals are arithmetic over fictional values. No background agent or service performs work on the visitor’s behalf.

Do not reuse Attio’s logo, typography files, customer names, customer stories, product screenshots, transcript content, statistics, source code, or commercial copy. The reference inspires clarity, hierarchy, and the importance of connected context. Circa’s original identity is cobalt-led, with a relationship-map hero and a practical white workspace. Exact reference animation durations were not measured, so any transitions in Circa are independent design choices.

## Original identity and typography

Use a compact lowercase Circa wordmark paired with an original circular mark: a cobalt ring with a small connected dot. The mark suggests a relationship rather than a mail app, sales funnel, or generic spark. Use locally licensed Geist for the complete site. Headings should be medium-to-semibold with tight tracking and a compact line height. Keep the look precise and contemporary, without borrowing a serif editorial treatment from other categories in the collection.

The main canvas is white. Ink is near #171c27, cobalt near #315be8, and supporting text near #526078. Light panels use cool gray-blue surfaces such as #f5f7fb and #edf2ff. Borders are pale blue-gray, with darker outlines on actual controls. Avoid cream, sage, rust, and warm-paper styling as the dominant identity. A small amount of pale blue is enough to communicate grouping and selected state without coloring every panel.

All controls and navigation must be at least fourteen pixels. Secondary metadata may be twelve, while body reading should be around sixteen to eighteen. Do not make a large desktop product preview legible only by magnification. On mobile, reflow the preview and live workspace into readable cards. Use moderate six-to-ten-pixel radii and restrained shadows. The interface should feel like a useful system, not a pile of promotional cards.

The language is direct: People, Companies, Pipeline, Saved view, Local note, Edit deal, Export shown CSV, and Workspace JSON. Avoid fake Sign in, Connect inbox, Send email, or Start trial actions when those flows do not exist. A clear Explore the demo action should lead directly to the working records. The product’s limitations are stated where they affect a decision, not hidden behind a vague disclaimer.

## Marketing composition

The header is a white bar around seventy-eight pixels high with the Circa identity at the left and concise navigation at the right. Include The workspace, How it works, and Explore the demo. The main action is cobalt with white text. A fine rule separates the header from the page. On mobile, allow the header to wrap and keep the actual navigation readable rather than reducing it to tiny labels.

The hero is a two-column composition. The left contains a small uppercase description, a large two-line headline, explanatory copy, and two actions. The headline reads People first, then Context always, with the second line in cobalt. At a typical large laptop width it is around seventy-six pixels, with a 1.03 line height and restrained negative tracking. Supporting text explains the relationship between people, companies, opportunities, and useful details.

The right side is an original relationship map, not a borrowed product screenshot. A dotted cool-blue field and concentric outline circles create a calm structure. White record cards are linked by thin blue paths: two fictional people connect to Northline Studio, which connects to a partnership opportunity and a context note. Each card is a real link to a corresponding demo page or guide section. Labels remain readable, and the map should not imply an automated inference that the local product does not perform.

The map can lift a card slightly on hover, but it should not animate continuously. On a narrow phone, remove the decorative connector lines and arrange the cards vertically, with the company first. This retains the actual relationships without shrinking the desktop map. The people, company, and opportunity labels remain at normal interface sizes. The original SVG thumbnail may show the desktop composition, but the live page is native HTML and CSS.

Below the hero, include a restrained capability rail and a broad product preview showing a few fictional people rows. The preview has a small navigation column, a clear table-like structure, and links into the actual records. Follow with three substantial feature explanations: connect records, keep a useful view, and make a deal change explicit. A dark ink section shows a company context card. Native FAQ disclosures and a closing workspace invitation complete the page. Do not add invented customer logos or success metrics.

## Fictional record model

The source data contains four people, three companies, and four deals. People have stable identifiers, names, initials, roles, company links, relationship states, reserved-domain email addresses, fictional locations, and contextual summaries. The people are Mara Quinn, Ivo Chen, Leah Moss, and Omar Reed. Their names and roles are original demonstration content and should never be described as real contacts discovered from an account.

Northline Studio is an invented design practice with twelve team members in West District. Mara and Ivo belong to it. Arcwell Labs is an invented software company with thirty-two team members in Central Quarter, linked to Leah. Common Ground is an invented consulting practice with eight team members in North Shore, linked to Omar. These locations are fictional context labels, not precise addresses. Company domains use the reserved .example namespace and are displayed as text rather than outbound contact links.

The four deals are Northline partnership, Arcwell platform study, Common Ground workshop, and Northline identity extension. Each has a company, primary person, stage, fictional value in GBP, owner, and explanatory context. The starting stages are Discovery, Proposal, New, and Won respectively. The original values are £18,000, £36,000, £8,500, and £12,000. These are interface data, not actual revenue, forecasts, prices, or agreements.

Keep source relationships immutable in this demo. The visitor can add notes and change deal stage/value, but cannot accidentally detach a person from a company or create an unknown relationship through an unvalidated field. The stable identifiers allow record links, saved notes, and deal changes to survive navigation and reloads. Unknown record identifiers should produce a clear not-found state rather than silently displaying a different contact.

## Workspace shell

The working pages share a precise application layout beneath the global header. A narrow left sidebar identifies the demo workspace and links to People, Companies, and Pipeline. The sidebar counts reflect the fixed source collections. The main content area has a page heading, a concise explanation, export actions, and a local-demo notice. The notice states that records are fictional and no external messages, enrichment, or CRM updates occur.

On desktop, the sidebar is around one hundred and ninety pixels wide, leaving generous space for tables and record panels. At narrower widths it becomes a horizontal navigation row. Do not keep a tiny fixed sidebar on a phone. The main content uses min-width zero so native inputs and table content cannot force document overflow. The working interface remains white with cool gray-blue grouping surfaces.

The People page and Companies page use actual data tables on desktop and readable record cards on mobile. Only one representation is visible at a time. Table headers are meaningful and use column scopes. People columns are Person, Role, Company, Relationship, and Notes. Company columns are Company, Industry, Team size, Location, and People. Names and company relationships are real internal links. Email addresses do not become mailto actions.

## Filters and saved views

People filters combine a text query, a relationship state, and a company. Text matching covers name, role, and company name. Company filters combine a query and industry, with the query matching name, industry, and fictional location. Use case-insensitive substring matching. All filters combine with AND semantics. A query for Leah while Northline Studio is selected should show no results rather than ignoring the company filter.

Show a result count and a useful empty state. Reset clears all filters and returns to the full collection. Filtering does not alter the underlying data or notes. It should not call an external search service, infer relationship importance, or generate scores. The currently visible records are the records exported by Export shown CSV, so the export matches the view the visitor is using.

A saved view records the current entity, query, selected filter, and company when applicable. The visitor gives it a name of up to fifty characters. Names cannot consist only of whitespace. Saving a name that already exists for the same entity updates that view rather than creating a duplicate. Saved views for People should not appear in Companies, and vice versa. The view selector includes an Unsaved view state.

Selecting a saved view restores the controls and rerenders the list. Changing any filter marks the current selection as unsaved so the name does not imply that modified criteria have already been persisted. The saved view itself remains available until deliberately updated. Reloading preserves the saved options; selecting one restores its criteria. Invalid stored filter values should fall back to valid controls rather than crashing or hiding the entire list unexpectedly.

## Linked person and company details

Use person.html with a recognized id query for person records and company.html with a recognized id for companies. A missing query can open the first valid demonstration record. An unknown explicit identifier should show Record not found with a path back to the appropriate library. Do not use a guessed record or leave stale placeholder details visible under an invalid URL.

A person detail includes initials, name, role, relationship context, fictional email, state, location, its connected company, relevant deals, and a local note history. A company detail includes its name, industry, fictional domain, team size, location, connected people, related opportunities, and its own notes. The links work in both directions. A visitor can move from Mara to Northline and then to Ivo without returning to a global search.

The right-hand facts panel includes a count of relevant deals and the sum of their current fictional values. Those numbers derive from the same edited deal data used in the pipeline. If a value changes in the pipeline, reopening the company or person record should show the new total. Do not maintain a separate stale copy of the value on record pages. Label the sum as fictional deal value rather than a forecast.

The layout stacks on mobile, with the facts panel presented as a readable block and the relationship context, linked records, opportunities, and notes following naturally. Long reserved-domain addresses and user notes should wrap without pushing the page wider. The person’s name is the page h1; related-section headings are h2; deal titles can be h3. Keep the heading structure logical rather than using heading levels only for visual size.

## Local notes

Each person and company has a separate note key. A note contains plain text and a timestamp. The visitor writes it in a visible textarea and saves it through a real form. Empty or whitespace-only notes are rejected. Clear custom validity when the user edits the field so the correction flow works after an invalid submission. The note text is limited to two thousand characters and rendered with textContent, never interpolated as HTML.

Saved notes appear newest first, with a readable timestamp and a local-note label. The note history survives reloads when browser storage is available. A note does not send a message, create an external task, schedule a reminder, or notify the fictional contact. The success message should say that a local note was saved and that no message was sent. Do not add a Send button or compose-email icon beside an example address.

People and company CSV exports include the latest local note so the export is useful and exercises safe handling of user-entered text. Workspace JSON includes the complete retained note history. Notes must remain associated with the correct record when navigating between people and companies. A company note is not automatically copied to every person, and a person’s note should not appear on an unrelated company.

## Deal pipeline and editing

The pipeline has four stages: New, Discovery, Proposal, and Won. Display them as columns at a wide viewport, then use two columns at intermediate sizes and one column on a phone. Each stage shows the number of visible deals and a simple sum of their fictional values. Each card contains company context, deal title, value, primary person link, owner, and an explicit Edit deal action.

Provide an owner filter with All owners, Ari, and Jules. The count and total should describe the filtered deals, and Export shown CSV should use that same filtered set. Do not label the sum as expected revenue or a prediction. These are invented amounts for demonstrating data relationships. The total changes only because a user edits a value or changes the visible owner filter.

Editing opens a native dialog with the deal name, its context, a stage select, and a numeric value field. The accepted value range is zero to one million GBP in increments of one hundred. Validate that the value is finite and in range, while retaining native number-field validation. Cancel and Escape leave the deal unchanged. Saving updates only that deal’s stage and value, persists the change locally, rerenders the pipeline, and updates the total.

Use a form instead of requiring drag-and-drop. It makes the change precise and accessible to keyboard and touch users. When the updated board replaces the original edit button, return focus to the stable pipeline heading. The dialog should fit inside a narrow viewport and use sixteen-pixel inputs and fourteen-pixel actions. No external CRM is changed and no commercial agreement is created.

## Safe exports

Two export types are supported. Export shown CSV uses the currently visible People, Companies, or Pipeline records. Workspace JSON includes the complete fictional source collections, current edited deals, local notes, and saved views. Label these scopes clearly so the visitor does not mistake a filtered CSV for a full backup or a workspace JSON file for one record alone.

Use one CSV cell serializer for every value. Convert values to text, quote every cell, double embedded quotation marks, and use CRLF row endings. If a text cell begins with optional whitespace followed by a spreadsheet formula marker such as equals, plus, minus, or at-sign, prefix it as literal text. This is important because a local note may contain user-entered content. Do not rely only on surrounding the value with double quotes, because spreadsheet applications can still interpret quoted formula-like values.

JSON export uses ordinary JSON serialization and is displayed as inert text. Both export types open a native preview dialog with a read-only textarea, a real Blob-backed download anchor, Copy export, and Close. The filename and MIME type match the chosen format. Revoke an earlier Blob URL before preparing a replacement. The background is inert while the modal is open, and focus returns to the export button when it closes.

Copy reports the actual Clipboard API result. If copying fails, focus and select the visible preview text and keep the download link available. Preparing a file does not upload it or transmit it to another person. Do not claim a file was downloaded or imported unless that completion was observed. The preview should expose the exact safe CSV representation, including the literal-text prefix when relevant.

## Storage and defensive loading

Store local changes under one dedicated Circa workspace key. Persist note collections, deal overrides, and saved views. On load, validate record identifiers against the known source maps. Accept only recognized deal stages and finite values in the allowed range. Validate note text and timestamps as strings, limit restored lengths, and ignore entries belonging to unknown records. Saved-view entities must be People or Companies, with valid string fields.

Malformed storage should fall back to a safe empty local-change state, leaving the original fictional records available. If writing storage fails, retain the current in-memory changes and tell the visitor to export JSON before leaving. Do not claim a successful save when the browser rejects it. The demo has no account, server persistence, synchronization, credential store, or external API connection.

## Guide and content depth

The guide explains linked records, combined filters, saved views, local notes, deal edits, safe exports, and the local boundary. It should use the actual fictional examples from the workspace. For example, Northline has Mara and Ivo and two opportunities; a saved company filter returns those two people; an Active relationship filter narrows that result to Mara. The explanation should match the implemented data exactly.

Explain that a status is a label, while a note can hold a question or decision. Explain that a deal stage and value are edited manually and that totals are simple arithmetic. Describe the CSV formula-safety treatment and the difference between a visible-record export and a full workspace export. Do not introduce hypothetical enrichment, email synchronization, AI scoring, automated workflows, or enterprise controls.

The guide remains a readable editorial page with a compact section navigation. Its body text is around sixteen to seventeen pixels, and its headings are large enough to scan. It provides useful next actions back into the workspace. It should not be a placeholder FAQ or a marketing page that repeats the hero proposition without explaining the actual behavior.

## Responsive and accessibility quality

At around eleven hundred pixels, reduce workspace spacing and use two pipeline columns. At around eight hundred and fifty, turn the sidebar into a horizontal navigation row, replace desktop record tables with cards, and stack record-detail panels. At phone widths, reflow the relationship map and product preview into readable blocks, stack filters and saved-view controls, and use one pipeline column. Do not hide overflow globally to mask an oversized table or graph.

Every page has a skip link, one main landmark, and a logical heading structure. Forms have visible labels, native validation, and clear correction paths. Buttons change state; links navigate. Dialogs use native focus containment and Escape handling. Record links identify the destination by name. Fictional addresses remain plain text. Selected views, stages, and filters should not depend on color alone.

Motion is restrained and optional: a relationship card can lift slightly on hover, and buttons can change color. There are no infinite animations, fake agents typing, or background processing indicators. Reduced-motion preferences remove transitions and smooth scrolling. The product should feel finished and understandable in its static state.

## Delivery and verification

Export buildCirca from scripts/circa/build.mjs and return an object containing prompt. Generate seven complete pages: home, People, Companies, person detail, company detail, Pipeline, and guide. Keep source in scripts/circa, output and assets in public/demos/circa, and the distributed prompt in public/prompts/circa.md. Append the exact fictional data, CSV safety helpers, responsive CSS, and actual JavaScript under Reference interaction handler. Do not edit shared catalogs, application files, validators, or deployment configuration.

Deliver an original relationship-map hero SVG and raster hero.webp, the local Geist font and license, and provider-neutral credits. No favicon, real contact data, external messaging, or copied brand assets should appear. The parent task owns integration and publishing.

Run scoped lint and test the CSV serializer with commas, quotation marks, newlines, leading whitespace, and formula-like strings. In the browser, combine filters, save and restore a view, navigate linked records, add and reload a note, edit a deal stage and value, verify linked totals, filter by owner, export the visible CSV, and inspect a workspace JSON preview. Test invalid record handling and dialog cancellation. Measure all narrow pages for overflow and control sizes, and report only actual observed outcomes.

The finished site should make relationship context easy to follow and local changes easy to trust. Its visual distinction comes from precise white space, cobalt connections, and a real linked-record model rather than decorative complexity.


## Exact fictional CRM data and CSV safety logic

```javascript
export const stages = ['New', 'Discovery', 'Proposal', 'Won'];
export const people = [
  {
    id: 'mara',
    name: 'Mara Quinn',
    initials: 'MQ',
    role: 'Operations lead',
    company: 'northline',
    status: 'Active',
    email: 'mara@northline.example',
    location: 'West District',
    summary:
      'Mara is the fictional operations lead at Northline Studio. Her record provides context for a potential partnership and an existing identity project. The relationships below connect the person, company, and relevant work without relying on an email thread.',
  },
  {
    id: 'ivo',
    name: 'Ivo Chen',
    initials: 'IC',
    role: 'Design director',
    company: 'northline',
    status: 'Follow-up',
    email: 'ivo@northline.example',
    location: 'West District',
    summary:
      'Ivo is the fictional design director at Northline Studio. His record focuses on the creative context around the identity extension. Notes can capture useful questions and decisions, while the company link keeps the wider relationship visible.',
  },
  {
    id: 'leah',
    name: 'Leah Moss',
    initials: 'LM',
    role: 'Product lead',
    company: 'arcwell',
    status: 'New',
    email: 'leah@arcwell.example',
    location: 'Central Quarter',
    summary:
      'Leah is the fictional product lead at Arcwell Labs. The linked platform study is an example opportunity in the proposal stage. This record is a place for context and local notes, not a channel for contacting an actual person.',
  },
  {
    id: 'omar',
    name: 'Omar Reed',
    initials: 'OR',
    role: 'Founder',
    company: 'common-ground',
    status: 'Active',
    email: 'omar@commonground.example',
    location: 'North Shore',
    summary:
      'Omar is the fictional founder of Common Ground. The relationship begins with an exploratory workshop opportunity. The record shows how a small contact history can remain connected to a company and its next possible piece of work.',
  },
];
export const companies = [
  {
    id: 'northline',
    name: 'Northline Studio',
    initials: 'NS',
    industry: 'Design',
    size: 12,
    location: 'West District',
    domain: 'northline.example',
    summary:
      'An invented independent design practice working across identity and digital projects. Two fictional contacts and two example deals make Northline a useful record for exploring connected relationship context.',
  },
  {
    id: 'arcwell',
    name: 'Arcwell Labs',
    initials: 'AL',
    industry: 'Software',
    size: 32,
    location: 'Central Quarter',
    domain: 'arcwell.example',
    summary:
      'An invented software team exploring a new platform direction. The company record connects its product lead, a proposal-stage opportunity, and any notes you choose to add in this browser.',
  },
  {
    id: 'common-ground',
    name: 'Common Ground',
    initials: 'CG',
    industry: 'Consulting',
    size: 8,
    location: 'North Shore',
    domain: 'commonground.example',
    summary:
      'An invented consulting practice considering a collaborative workshop. The record demonstrates how even an early relationship can have a clear owner, a linked person, and a place for context.',
  },
];
export const deals = [
  {
    id: 'partnership',
    name: 'Northline partnership',
    company: 'northline',
    person: 'mara',
    stage: 'Discovery',
    value: 18000,
    owner: 'Ari',
    context:
      'Explore a possible collaboration across operations and delivery. The amount is fictional demo data, not a forecast or agreement.',
  },
  {
    id: 'platform-study',
    name: 'Arcwell platform study',
    company: 'arcwell',
    person: 'leah',
    stage: 'Proposal',
    value: 36000,
    owner: 'Jules',
    context:
      'A fictional proposal-stage project used to demonstrate deal editing and linked account context.',
  },
  {
    id: 'workshop',
    name: 'Common Ground workshop',
    company: 'common-ground',
    person: 'omar',
    stage: 'New',
    value: 8500,
    owner: 'Ari',
    context:
      'An exploratory workshop opportunity. No actual offer, booking, or commercial commitment is represented.',
  },
  {
    id: 'identity-extension',
    name: 'Northline identity extension',
    company: 'northline',
    person: 'ivo',
    stage: 'Won',
    value: 12000,
    owner: 'Jules',
    context:
      'A fictional completed opportunity that gives the pipeline a contrasting state. Won is demo status only.',
  },
];
export const money = (value) => '£' + value.toLocaleString('en-GB');
export function csvCell(value) {
  const text = String(value ?? '');
  const safe = /^\s*[=+\-@]/.test(text) ? "'" + text : text;
  return '"' + safe.replaceAll('"', '""') + '"';
}
export function csvText(rows) {
  return rows.map((row) => row.map(csvCell).join(',')).join('\r\n') + '\r\n';
}

```

## Exact responsive CSS

```css
@font-face {
  font-family: Circa;
  src: url('assets/geist-variable.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}
* {
  box-sizing: border-box;
}
html {
  scroll-behavior: smooth;
  scroll-padding-top: 90px;
}
body {
  margin: 0;
  background: #fff;
  color: #171c27;
  font:
    16px/1.65 Circa,
    Arial,
    sans-serif;
  -webkit-font-smoothing: antialiased;
}
h1,
h2,
h3,
h4,
p {
  margin: 0;
}
h1,
h2,
h3 {
  font-weight: 580;
  letter-spacing: -0.045em;
  line-height: 1.1;
}
button,
input,
select,
textarea {
  font: inherit;
}
button {
  cursor: pointer;
  color: inherit;
}
a {
  color: inherit;
  text-decoration: none;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 3px solid #315be8;
  outline-offset: 4px;
}
::selection {
  background: #dce5ff;
}
.skip {
  position: fixed;
  top: -90px;
  left: 18px;
  z-index: 99;
  padding: 14px;
  background: #fff;
  border: 1px solid #315be8;
}
.skip:focus {
  top: 0;
}
.header {
  height: 78px;
  border-bottom: 1px solid #dde1e8;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 5%;
  gap: 25px;
  background: #fff;
}
.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 30px;
  font-weight: 650;
  letter-spacing: -1.5px;
}
.brand-icon {
  width: 27px;
  height: 27px;
  border: 5px solid #315be8;
  border-radius: 50%;
  position: relative;
}
.brand-icon:after {
  content: '';
  position: absolute;
  width: 8px;
  height: 8px;
  background: #315be8;
  border: 3px solid #fff;
  border-radius: 50%;
  right: -9px;
  bottom: -6px;
}
.header nav {
  display: flex;
  align-items: center;
  gap: 30px;
  font-size: 14px;
}
.header nav a:hover {
  text-decoration: underline;
  text-underline-offset: 5px;
}
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 25px;
  background: #315be8;
  color: #fff;
  border: 1px solid #315be8;
  border-radius: 6px;
  padding: 12px 18px;
  min-height: 46px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
}
.button.secondary {
  background: #fff;
  color: #242d40;
  border-color: #aeb8ca;
}
.button:hover {
  background: #2347c4;
  color: #fff;
  border-color: #2347c4;
}
.eyebrow {
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #58657c;
  display: block;
  line-height: 1.7;
}
.hero {
  padding: 76px 5% 70px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6%;
  align-items: center;
  border-bottom: 1px solid #dde1e8;
  overflow: hidden;
}
.hero h1 {
  font-size: clamp(51px, 5.2vw, 76px);
  line-height: 1.03;
  letter-spacing: -0.055em;
  margin: 26px 0;
}
.hero h1 span {
  color: #315be8;
}
.hero p {
  font-size: 18px;
  line-height: 1.8;
  max-width: 525px;
  color: #526078;
  margin-bottom: 28px;
}
.actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
.hero .fineprint {
  font-size: 12px;
  line-height: 1.8;
  margin-top: 20px;
}
.relationship-map {
  position: relative;
  height: 470px;
  min-width: 0;
  background: radial-gradient(#c8d4ef 1px, transparent 1px);
  background-size: 22px 22px;
  border: 1px solid #e0e6f0;
  border-radius: 10px;
}
.relationship-map:before,
.relationship-map:after {
  content: '';
  position: absolute;
  width: 290px;
  height: 290px;
  border: 1px solid #d4dff6;
  border-radius: 50%;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
.relationship-map:after {
  width: 390px;
  height: 390px;
}
.map-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.map-lines path {
  stroke: #8fa9ef;
  stroke-width: 2;
  fill: none;
}
.map-node {
  position: absolute;
  background: #fff;
  border: 1px solid #bbc9e3;
  border-radius: 8px;
  box-shadow: 0 8px 20px #315be811;
  padding: 17px;
  z-index: 1;
  transition: transform 0.25s;
}
.map-node:hover {
  transform: translateY(-4px);
}
.map-node strong {
  display: block;
  font-size: 17px;
  letter-spacing: -0.4px;
  font-weight: 550;
  margin-top: 8px;
}
.map-node small {
  display: block;
  font-size: 12px;
  color: #5c6a82;
  margin-top: 3px;
}
.map-node .avatar {
  margin-bottom: 2px;
}
.map-node.company {
  left: 31%;
  top: 34%;
  width: 225px;
  border: 2px solid #315be8;
}
.map-node.person-a {
  left: 5%;
  top: 7%;
  width: 185px;
}
.map-node.person-b {
  right: 4%;
  top: 8%;
  width: 175px;
}
.map-node.deal {
  left: 10%;
  bottom: 6%;
  width: 215px;
}
.map-node.context {
  right: 3%;
  bottom: 8%;
  width: 165px;
  background: #edf2ff;
}
.avatar {
  width: 35px;
  height: 35px;
  border-radius: 7px;
  background: #e5ecff;
  color: #3153b6;
  display: inline-grid;
  place-items: center;
  font-size: 12px;
  font-weight: 600;
  flex-shrink: 0;
}
.avatar.large {
  width: 60px;
  height: 60px;
  font-size: 20px;
  border-radius: 11px;
}
.capability-rail {
  display: flex;
  justify-content: space-between;
  gap: 25px;
  flex-wrap: wrap;
  padding: 25px 5%;
  border-bottom: 1px solid #dde1e8;
  font-size: 14px;
  color: #56627a;
}
.section {
  padding: 80px 5%;
}
.section-head {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 12%;
  align-items: end;
  margin-bottom: 40px;
}
.section-head h2 {
  font-size: clamp(37px, 4.3vw, 62px);
}
.section-head p {
  font-size: 17px;
  color: #526078;
  line-height: 1.8;
}
.product-preview {
  border: 1px solid #cbd4e4;
  border-radius: 10px;
  overflow: hidden;
  background: #fff;
}
.preview-top {
  padding: 18px 25px;
  background: #f6f8fc;
  border-bottom: 1px solid #dce2ed;
  display: flex;
  justify-content: space-between;
  gap: 25px;
  font-size: 14px;
}
.preview-grid {
  display: grid;
  grid-template-columns: 190px 1fr;
}
.preview-menu {
  padding: 24px;
  background: #f8f9fc;
  border-right: 1px solid #dce2ed;
  display: grid;
  align-content: start;
  gap: 12px;
  font-size: 14px;
}
.preview-menu span {
  padding: 9px 11px;
}
.preview-menu .active {
  background: #e4ebfc;
  border-radius: 5px;
  color: #3153b6;
}
.preview-records {
  padding: 20px 25px;
}
.preview-row {
  display: grid;
  grid-template-columns: 1.1fr 1fr 1fr;
  gap: 20px;
  padding: 18px 0;
  border-bottom: 1px solid #e0e5ef;
  font-size: 14px;
  align-items: center;
}
.preview-row:first-child {
  font-size: 12px;
  color: #5b6982;
}
.preview-row .name {
  display: flex;
  gap: 12px;
  align-items: center;
}
.status-pill {
  display: inline-flex;
  padding: 4px 8px;
  border: 1px solid #ccd7ef;
  border-radius: 4px;
  font-size: 12px;
  background: #edf2ff;
  color: #385698;
  width: max-content;
}
.feature-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 30px;
}
.feature {
  border-top: 1px solid #aab8d1;
  padding-top: 25px;
}
.feature h3 {
  font-size: 27px;
  margin: 25px 0 17px;
}
.feature p {
  font-size: 16px;
  line-height: 1.85;
  color: #526078;
}
.feature-visual {
  margin-top: 28px;
  background: #f2f5fd;
  border: 1px solid #d4def2;
  padding: 22px;
  min-height: 160px;
}
.feature-visual .mini-line {
  height: 8px;
  background: #ced9f2;
  margin: 12px 0;
}
.feature-visual .mini-line.short {
  width: 65%;
}
.feature-visual strong {
  font-size: 17px;
  display: block;
  margin: 8px 0;
}
.feature-visual p {
  font-size: 14px;
}
.dark-section {
  background: #171f32;
  color: #f5f7ff;
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: 12%;
  align-items: center;
}
.dark-section h2 {
  font-size: clamp(40px, 4.7vw, 67px);
  margin: 22px 0;
}
.dark-section .eyebrow {
  color: #bbc9e6;
}
.dark-section p {
  font-size: 17px;
  line-height: 1.85;
  color: #c4cee2;
}
.context-card {
  padding: 30px;
  background: #fff;
  color: #17223b;
  border-radius: 8px;
}
.context-card .eyebrow {
  color: #526078;
}
.context-card h3 {
  font-size: 27px;
  margin: 18px 0;
}
.context-card p {
  font-size: 16px;
  color: #526078;
}
.context-card dl {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 15px;
  margin-top: 27px;
  font-size: 14px;
}
.context-card dt {
  color: #5e6b82;
}
.context-card dd {
  margin: 0;
}
.faq {
  max-width: 850px;
  margin: auto;
}
.faq h2 {
  font-size: 43px;
  margin-bottom: 30px;
}
.faq details {
  padding: 22px 0;
  border-top: 1px solid #c9d2e2;
}
.faq summary {
  font-size: 17px;
  cursor: pointer;
}
.faq p {
  font-size: 16px;
  color: #526078;
  line-height: 1.85;
  margin-top: 16px;
}
.closing {
  padding: 60px 5%;
  background: #edf2ff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 40px;
}
.closing h2 {
  font-size: clamp(37px, 4.2vw, 59px);
  max-width: 760px;
}
.footer {
  padding: 30px 5%;
  display: flex;
  justify-content: space-between;
  gap: 30px;
  border-top: 1px solid #d8dfeb;
  font-size: 12px;
  color: #526078;
}
.footer nav {
  display: flex;
  gap: 25px;
  font-size: 14px;
}
.workspace {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  min-height: calc(100vh - 78px);
}
.sidebar {
  border-right: 1px solid #dde3ed;
  background: #f8f9fc;
  padding: 30px 18px;
  align-self: stretch;
}
.sidebar-label {
  font-size: 12px;
  letter-spacing: 0.8px;
  color: #5c6a83;
  margin: 0 10px 24px;
  display: block;
}
.sidebar nav {
  display: grid;
  gap: 8px;
}
.sidebar a {
  font-size: 14px;
  padding: 11px 13px;
  border-radius: 5px;
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
.sidebar a[aria-current='page'] {
  background: #e7edfc;
  color: #294db4;
  font-weight: 550;
}
.sidebar .local {
  font-size: 12px;
  color: #5b6982;
  padding: 25px 10px;
  line-height: 1.8;
}
.workspace-content {
  padding: 35px 36px 65px;
  min-width: 0;
}
.workspace-top {
  display: flex;
  justify-content: space-between;
  align-items: end;
  gap: 25px;
  margin-bottom: 25px;
}
.workspace-top h1 {
  font-size: 42px;
  margin: 9px 0 14px;
}
.workspace-top p {
  font-size: 16px;
  line-height: 1.8;
  color: #526078;
  max-width: 660px;
}
.local-notice {
  font-size: 12px;
  line-height: 1.8;
  padding: 12px 16px;
  background: #f2f5fb;
  border: 1px solid #d8e0ee;
  border-radius: 5px;
  margin-bottom: 28px;
  color: #596984;
}
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  align-items: end;
  margin-bottom: 20px;
}
.filter-bar label {
  display: grid;
  gap: 7px;
  font-size: 14px;
}
.filter-bar input,
.filter-bar select {
  background: #fff;
  border: 1px solid #aebbd1;
  border-radius: 5px;
  min-height: 45px;
  padding: 10px 12px;
  font-size: 14px;
  color: #273650;
  max-width: 100%;
}
.filter-bar input {
  width: 260px;
}
.views-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 20px;
  padding: 20px 0;
  border-top: 1px solid #dce2ec;
  border-bottom: 1px solid #dce2ec;
  margin: 23px 0;
}
.views-bar label {
  font-size: 14px;
  display: grid;
  gap: 7px;
}
.views-bar select,
.views-bar input {
  min-height: 43px;
  padding: 9px 12px;
  font-size: 14px;
  border: 1px solid #aebbd1;
  border-radius: 5px;
  background: #fff;
  color: #273650;
}
.save-view-form {
  display: flex;
  gap: 10px;
  align-items: end;
  flex-wrap: wrap;
}
.save-view-form input {
  width: 210px;
}
.count-line {
  font-size: 14px;
  color: #526078;
  margin: 20px 0;
}
.records-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: auto;
}
.records-table th,
.records-table td {
  text-align: left;
  border-bottom: 1px solid #dde3ed;
  padding: 18px 13px;
  font-size: 14px;
  vertical-align: middle;
}
.records-table th {
  font-weight: 500;
  color: #596984;
  background: #f8f9fc;
  white-space: nowrap;
}
.records-table td:first-child {
  white-space: nowrap;
}
.records-table td:first-child .avatar {
  margin-right: 12px;
}
.record-link {
  font-size: 14px;
  color: #234fc4;
  text-decoration: underline;
  text-underline-offset: 4px;
  overflow-wrap: anywhere;
}
.record-cards {
  display: none;
}
.record-card {
  border: 1px solid #ccd6e7;
  border-radius: 7px;
  padding: 22px;
  background: #fff;
}
.record-card > .record-link {
  font-size: 20px;
  font-weight: 500;
  display: inline-block;
  margin-left: 12px;
}
.record-card dl {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: 12px;
  font-size: 16px;
  margin: 25px 0 0;
}
.record-card dt {
  font-size: 14px;
  color: #5b6982;
}
.record-card dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.empty {
  padding: 30px;
  background: #f2f5fc;
  border: 1px solid #d4deef;
  border-radius: 6px;
}
.empty h2,
.empty h3 {
  font-size: 27px;
  margin-bottom: 18px;
}
.empty p {
  font-size: 16px;
  line-height: 1.85;
  color: #526078;
}
.status {
  font-size: 14px;
  color: #31549c;
  display: block;
  min-height: 25px;
  margin: 20px 0;
  line-height: 1.8;
}
.record-heading {
  display: flex;
  align-items: center;
  gap: 19px;
  margin-bottom: 20px;
}
.record-heading h1 {
  font-size: 43px;
  margin: 4px 0 8px;
}
.record-heading p {
  font-size: 16px;
  color: #58677f;
}
.record-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(260px, 0.8fr);
  gap: 30px;
  align-items: start;
}
.record-main {
  min-width: 0;
}
.record-main > h2,
.record-aside h2 {
  font-size: 23px;
  margin: 0 0 20px;
  letter-spacing: -0.7px;
}
.record-main > p {
  font-size: 17px;
  line-height: 1.85;
  color: #526078;
  margin-bottom: 30px;
}
.record-aside {
  padding: 24px;
  border: 1px solid #d0daeb;
  border-radius: 7px;
  background: #f7f9fd;
}
.record-aside dl {
  margin: 0 0 30px;
  font-size: 14px;
}
.record-aside dt {
  color: #596984;
  margin-top: 18px;
}
.record-aside dd {
  margin: 6px 0 0;
  overflow-wrap: anywhere;
  font-size: 16px;
}
.record-aside .metric {
  display: flex;
  justify-content: space-between;
  padding: 13px 0;
  border-top: 1px solid #d2dced;
  font-size: 14px;
}
.record-aside .metric strong {
  font-size: 17px;
  font-weight: 550;
}
.linked-records {
  padding: 22px;
  border: 1px solid #d0daeb;
  border-radius: 7px;
  margin-bottom: 30px;
}
.linked-records h2 {
  font-size: 22px;
  margin-bottom: 17px;
}
.linked-person {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  border-top: 1px solid #e0e6ef;
  padding: 14px 0;
  font-size: 14px;
  color: #596984;
}
.linked-deals {
  display: grid;
  gap: 15px;
  margin-bottom: 35px;
}
.linked-deal {
  padding: 22px;
  border: 1px solid #d0daeb;
  border-radius: 6px;
}
.linked-deal h3 {
  font-size: 21px;
  margin-bottom: 10px;
  letter-spacing: -0.5px;
}
.linked-deal p {
  font-size: 14px;
  color: #526078;
  margin-bottom: 14px;
}
.text-link,
.text-button {
  display: inline-block;
  font-size: 14px;
  color: #244fc3;
  background: none;
  border: 0;
  border-bottom: 1px solid #a0b4e4;
  padding: 6px 0;
  text-align: left;
}
.note-form {
  margin: 20px 0 25px;
  padding: 23px;
  background: #f4f7fd;
  border: 1px solid #cedaee;
  border-radius: 7px;
}
.note-form label {
  font-size: 14px;
  display: block;
  margin-bottom: 8px;
}
.note-form textarea {
  width: 100%;
  min-height: 120px;
  padding: 12px;
  border: 1px solid #aebdd5;
  border-radius: 5px;
  background: #fff;
  font-size: 16px;
  margin-bottom: 17px;
  resize: vertical;
}
.note-card {
  padding: 22px 0;
  border-top: 1px solid #d7e0ed;
}
.note-card p {
  font-size: 16px;
  line-height: 1.8;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 12px 0;
}
.note-date {
  font-size: 12px;
  color: #64718a;
}
.pipeline-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: end;
  gap: 20px;
  margin: 25px 0;
}
.pipeline-toolbar label {
  display: grid;
  gap: 7px;
  font-size: 14px;
}
.pipeline-toolbar select {
  min-height: 45px;
  padding: 10px 12px;
  border: 1px solid #aab9d3;
  border-radius: 5px;
  font-size: 14px;
  background: #fff;
  color: #273650;
}
.pipeline {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px;
}
.stage-column {
  border: 1px solid #d3dceb;
  border-radius: 7px;
  background: #f5f7fb;
  padding: 15px;
  min-width: 0;
}
.stage-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.stage-heading h2 {
  font-size: 18px;
  letter-spacing: -0.3px;
}
.stage-heading span {
  font-size: 12px;
  display: grid;
  place-items: center;
  min-width: 23px;
  height: 23px;
  background: #e3eaf7;
  border-radius: 4px;
}
.stage-total {
  font-size: 12px;
  color: #5c6b85;
  margin: 13px 0 20px;
}
.deal-card {
  background: #fff;
  border: 1px solid #ccd7ea;
  border-radius: 6px;
  padding: 17px;
  margin-bottom: 15px;
}
.deal-card h3 {
  font-size: 21px;
  letter-spacing: -0.6px;
  line-height: 1.2;
  margin: 12px 0;
}
.deal-value {
  font-size: 21px;
  display: block;
  margin-bottom: 16px;
  letter-spacing: -0.5px;
  font-weight: 550;
}
.deal-owner {
  font-size: 12px;
  color: #5e6b83;
  margin: 15px 0;
}
.deal-card .eyebrow {
  font-size: 12px;
  letter-spacing: 0.4px;
}
.stage-empty {
  font-size: 14px;
  color: #5c6b85;
  line-height: 1.8;
}
.modal {
  width: 620px;
  max-width: calc(100vw - 32px);
  max-height: calc(100svh - 32px);
  border: 1px solid #9eb0d2;
  border-radius: 10px;
  padding: 28px;
  background: #fff;
  color: #17223b;
  box-shadow: 0 25px 80px #15274933;
}
.modal::backdrop {
  background: #17223b88;
}
.modal h2 {
  font-size: 30px;
  margin: 13px 0 20px;
}
.modal p {
  font-size: 16px;
  color: #526078;
  line-height: 1.8;
  margin-bottom: 22px;
}
.modal label {
  display: block;
  font-size: 14px;
  margin: 20px 0;
}
.modal input,
.modal select {
  display: block;
  width: 100%;
  min-height: 46px;
  background: #fff;
  border: 1px solid #aebdd5;
  border-radius: 5px;
  padding: 11px;
  margin-top: 8px;
  font-size: 16px;
  color: #273650;
}
.modal-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 25px;
}
.modal textarea {
  width: 100%;
  min-height: 300px;
  padding: 15px;
  border: 1px solid #aebdd5;
  border-radius: 5px;
  background: #f8faff;
  font:
    14px/1.75 Circa,
    sans-serif;
  resize: vertical;
}
.modal .status {
  margin-bottom: 0;
}
.guide-intro {
  padding: 55px 5% 35px;
}
.guide-intro h1 {
  font-size: clamp(47px, 5.3vw, 75px);
  max-width: 970px;
  margin: 23px 0;
}
.guide-intro p {
  font-size: 18px;
  line-height: 1.8;
  color: #526078;
  max-width: 760px;
}
.guide-body {
  display: grid;
  grid-template-columns: 230px minmax(0, 820px);
  gap: 8%;
  padding: 20px 5% 70px;
  justify-content: space-between;
}
.guide-nav {
  display: grid;
  gap: 16px;
  position: sticky;
  top: 25px;
  align-self: start;
  font-size: 14px;
}
.guide-body section {
  padding: 30px 0;
  border-top: 1px solid #c9d5e8;
}
.guide-body h2 {
  font-size: 32px;
  margin-bottom: 24px;
}
.guide-body p,
.guide-body li {
  font-size: 17px;
  line-height: 1.85;
  color: #526078;
  margin-bottom: 20px;
}
.guide-body ul {
  padding-left: 23px;
}
.guide-callout {
  padding: 22px;
  background: #edf2ff;
  border-left: 3px solid #315be8;
  font-size: 16px;
  line-height: 1.85;
}
.export-trigger-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
[hidden] {
  display: none !important;
}
@media (max-width: 1100px) {
  .hero {
    gap: 4%;
    padding-top: 60px;
  }
  .hero h1 {
    font-size: 61px;
  }
  .relationship-map {
    height: 430px;
  }
  .map-node.company {
    left: 25%;
    width: 205px;
  }
  .map-node.person-a {
    left: 3%;
    width: 155px;
  }
  .map-node.person-b {
    right: 3%;
    width: 155px;
  }
  .map-node.deal {
    left: 3%;
    width: 195px;
  }
  .map-node.context {
    width: 145px;
    right: 2%;
  }
  .workspace {
    grid-template-columns: 165px 1fr;
  }
  .workspace-content {
    padding: 30px 25px 55px;
  }
  .sidebar {
    padding: 25px 12px;
  }
  .pipeline {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .record-layout {
    grid-template-columns: 1fr 265px;
    gap: 22px;
  }
  .record-aside {
    padding: 20px;
  }
  .header nav {
    gap: 23px;
  }
  .section-head {
    gap: 8%;
  }
}
@media (max-width: 850px) {
  .hero {
    grid-template-columns: 1fr;
    padding: 45px 5%;
    gap: 35px;
  }
  .hero h1 {
    font-size: 66px;
    max-width: 690px;
  }
  .hero p {
    max-width: 650px;
  }
  .relationship-map {
    max-width: 640px;
    width: 100%;
    margin: auto;
    height: 450px;
  }
  .map-node.company {
    left: 31%;
  }
  .map-node.person-a {
    left: 5%;
    width: 180px;
  }
  .map-node.person-b {
    right: 4%;
    width: 175px;
  }
  .map-node.deal {
    left: 9%;
    width: 210px;
  }
  .map-node.context {
    right: 3%;
    width: 165px;
  }
  .workspace {
    grid-template-columns: 1fr;
  }
  .sidebar {
    padding: 13px 5%;
    border-right: 0;
    border-bottom: 1px solid #d8e0ed;
  }
  .sidebar-label,
  .sidebar .local {
    display: none;
  }
  .sidebar nav {
    display: flex;
    gap: 15px;
    flex-wrap: wrap;
  }
  .sidebar a {
    padding: 8px 12px;
    font-size: 14px;
  }
  .workspace-content {
    padding: 28px 5% 50px;
  }
  .records-table {
    display: none;
  }
  .record-cards {
    display: grid;
    gap: 20px;
  }
  .record-layout {
    grid-template-columns: 1fr;
    gap: 28px;
  }
  .record-aside {
    order: -1;
  }
  .record-aside dl {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 25px;
  }
  .record-aside dt {
    margin-top: 7px;
  }
  .record-aside dd {
    margin: 0 0 10px;
  }
  .preview-grid {
    grid-template-columns: 145px 1fr;
  }
  .preview-menu {
    padding: 20px 10px;
  }
  .preview-records {
    padding: 15px;
  }
  .preview-row {
    gap: 12px;
    font-size: 14px;
  }
  .section-head {
    grid-template-columns: 1fr;
    gap: 25px;
  }
  .feature-grid {
    grid-template-columns: 1fr;
    gap: 35px;
  }
  .feature-visual {
    min-height: 0;
    max-width: 700px;
  }
  .dark-section {
    grid-template-columns: 1fr;
    gap: 35px;
  }
  .closing {
    align-items: start;
    flex-direction: column;
    gap: 28px;
  }
  .guide-body {
    grid-template-columns: 1fr;
    gap: 25px;
  }
  .guide-nav {
    position: static;
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
  }
  .header nav {
    gap: 20px;
  }
  .header nav .secondary-nav-link {
    display: none;
  }
}
@media (max-width: 580px) {
  .header {
    height: auto;
    min-height: 76px;
    padding: 20px 5%;
    flex-wrap: wrap;
    gap: 20px;
  }
  .header nav {
    width: 100%;
    justify-content: space-between;
    font-size: 14px;
    gap: 15px;
  }
  .header nav .button {
    padding: 10px 13px;
    min-height: 43px;
  }
  .hero {
    padding: 40px 5%;
  }
  .hero h1 {
    font-size: 49px;
    letter-spacing: -0.05em;
  }
  .hero p {
    font-size: 17px;
  }
  .relationship-map {
    height: auto;
    background: #f5f8ff;
    display: grid;
    grid-template-columns: 1fr;
    gap: 15px;
    padding: 20px;
  }
  .relationship-map:before,
  .relationship-map:after,
  .map-lines {
    display: none;
  }
  .map-node {
    position: static !important;
    width: 100% !important;
    box-shadow: none;
    display: grid;
    grid-template-columns: 45px 1fr;
    gap: 0 12px;
    padding: 17px;
  }
  .map-node .avatar {
    grid-row: span 2;
  }
  .map-node strong {
    font-size: 17px;
    margin: 0;
  }
  .map-node small {
    font-size: 12px;
  }
  .map-node.context {
    display: block;
    padding: 17px;
  }
  .map-node.deal {
    display: block;
  }
  .map-node.company {
    order: -1;
  }
  .map-node:hover {
    transform: none;
  }
  .capability-rail {
    font-size: 14px;
    gap: 14px;
  }
  .section {
    padding: 50px 5%;
  }
  .section-head h2 {
    font-size: 39px;
  }
  .section-head p {
    font-size: 16px;
  }
  .preview-top {
    padding: 16px;
    flex-wrap: wrap;
    font-size: 14px;
    gap: 10px;
  }
  .preview-grid {
    grid-template-columns: 1fr;
  }
  .preview-menu {
    display: none;
  }
  .preview-records {
    padding: 0 17px;
  }
  .preview-row {
    grid-template-columns: 1fr;
    gap: 9px;
    padding: 20px 0;
    font-size: 16px;
  }
  .preview-row:first-child {
    display: none;
  }
  .preview-row .name {
    font-size: 16px;
  }
  .preview-row .status-pill {
    font-size: 12px;
  }
  .feature h3 {
    font-size: 29px;
  }
  .dark-section h2 {
    font-size: 42px;
  }
  .dark-section p {
    font-size: 16px;
  }
  .context-card {
    padding: 24px;
  }
  .context-card dl {
    gap: 13px;
    font-size: 14px;
  }
  .faq h2 {
    font-size: 38px;
  }
  .closing h2 {
    font-size: 39px;
  }
  .footer {
    flex-direction: column;
    gap: 20px;
  }
  .footer nav {
    gap: 20px;
    flex-wrap: wrap;
  }
  .workspace-top {
    align-items: start;
    flex-direction: column;
    gap: 20px;
  }
  .workspace-top h1 {
    font-size: 37px;
  }
  .workspace-top p {
    font-size: 16px;
  }
  .filter-bar {
    align-items: stretch;
    gap: 15px;
  }
  .filter-bar label {
    width: 100%;
  }
  .filter-bar input {
    width: 100%;
  }
  .filter-bar select {
    width: 100%;
  }
  .views-bar {
    display: block;
  }
  .views-bar > label {
    margin-bottom: 20px;
  }
  .save-view-form {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .save-view-form input {
    width: 100%;
  }
  .save-view-form .button {
    justify-self: start;
  }
  .record-card {
    padding: 21px;
  }
  .record-card > .record-link {
    font-size: 19px;
  }
  .record-card dl {
    grid-template-columns: 95px 1fr;
    font-size: 16px;
    gap: 11px;
  }
  .record-heading {
    gap: 15px;
    align-items: start;
  }
  .record-heading h1 {
    font-size: 35px;
  }
  .avatar.large {
    width: 49px;
    height: 49px;
    font-size: 17px;
  }
  .record-aside dl {
    grid-template-columns: 1fr;
    gap: 5px;
  }
  .record-aside dd {
    font-size: 16px;
    margin-bottom: 15px;
  }
  .record-main > p {
    font-size: 16px;
  }
  .linked-person {
    flex-direction: column;
    gap: 7px;
  }
  .pipeline-toolbar {
    align-items: stretch;
    flex-direction: column;
    gap: 20px;
  }
  .pipeline {
    grid-template-columns: 1fr;
    gap: 23px;
  }
  .stage-column {
    padding: 18px;
  }
  .stage-heading h2 {
    font-size: 23px;
  }
  .stage-total {
    font-size: 14px;
  }
  .deal-card {
    padding: 20px;
  }
  .deal-card h3 {
    font-size: 24px;
  }
  .deal-value {
    font-size: 24px;
  }
  .deal-owner {
    font-size: 14px;
  }
  .modal {
    padding: 24px;
  }
  .modal h2 {
    font-size: 28px;
  }
  .modal textarea {
    min-height: 240px;
  }
  .guide-intro {
    padding: 35px 5% 28px;
  }
  .guide-intro h1 {
    font-size: 47px;
  }
  .guide-intro p {
    font-size: 17px;
  }
  .guide-body h2 {
    font-size: 29px;
  }
  .guide-body p,
  .guide-body li {
    font-size: 16px;
  }
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *:before,
  *:after {
    animation: none !important;
    transition: none !important;
  }
}

```

## Reference interaction handler

```javascript
const money=(value) => '£' + value.toLocaleString('en-GB');
function csvCell(value) {
  const text = String(value ?? '');
  const safe = /^\s*[=+\-@]/.test(text) ? "'" + text : text;
  return '"' + safe.replaceAll('"', '""') + '"';
}
function csvText(rows) {
  return rows.map((row) => row.map(csvCell).join(',')).join('\r\n') + '\r\n';
}
const data = JSON.parse(document.getElementById('circa-data').textContent);
const peopleMap = new Map(data.people.map((person) => [person.id, person]));
const companyMap = new Map(
  data.companies.map((company) => [company.id, company]),
);
const dealMap = new Map(data.deals.map((deal) => [deal.id, deal]));
const storageKey = 'circa-workspace-v1';
function cleanNotes(raw) {
  const result = {};
  for (const [key, entries] of Object.entries(raw || {})) {
    const [kind, id] = key.split(':');
    if (
      !(kind === 'person'
        ? peopleMap.has(id)
        : kind === 'company' && companyMap.has(id)) ||
      !Array.isArray(entries)
    )
      continue;
    result[key] = entries
      .filter(
        (note) =>
          note &&
          typeof note.text === 'string' &&
          typeof note.created === 'string',
      )
      .map((note) => ({
        text: note.text.slice(0, 2000),
        created: note.created,
      }))
      .slice(-50);
  }
  return result;
}
function loadWorkspace() {
  try {
    const raw = JSON.parse(localStorage.getItem(storageKey) || '{}');
    const changes = {};
    for (const [id, change] of Object.entries(raw.deals || {}))
      if (
        dealMap.has(id) &&
        data.stages.includes(change?.stage) &&
        Number.isFinite(change.value) &&
        change.value >= 0 &&
        change.value <= 1000000 &&
        change.value % 100 === 0
      )
        changes[id] = { stage: change.stage, value: change.value };
    const views = Array.isArray(raw.views)
      ? raw.views
          .filter(
            (view) =>
              view &&
              typeof view.id === 'string' &&
              typeof view.name === 'string' &&
              ['people', 'companies'].includes(view.entity),
          )
          .map((view) => ({
            id: view.id,
            name: view.name.slice(0, 50),
            entity: view.entity,
            query:
              typeof view.query === 'string' ? view.query.slice(0, 100) : '',
            filter: typeof view.filter === 'string' ? view.filter : '',
            company: companyMap.has(view.company) ? view.company : '',
          }))
      : [];
    return { notes: cleanNotes(raw.notes), deals: changes, views };
  } catch {
    return { notes: {}, deals: {}, views: [] };
  }
}
const workspace = loadWorkspace();
let exportUrl = '';
let activeDeal = null;
let dealTrigger = null;
function say(message) {
  document
    .querySelectorAll('[data-status]')
    .forEach((el) => (el.textContent = message));
}
function save(message) {
  try {
    localStorage.setItem(storageKey, JSON.stringify(workspace));
    say(message);
    return true;
  } catch {
    say(
      'Changes are available on this page. Storage is unavailable; export JSON before leaving.',
    );
    return false;
  }
}
function make(tag, className, text) {
  const el = document.createElement(tag);
  if (className) el.className = className;
  if (text !== undefined) el.textContent = text;
  return el;
}
function recordLink(kind, id, label) {
  const link = make('a', 'record-link', label);
  link.href = kind + '.html?id=' + encodeURIComponent(id);
  return link;
}
function currentDeals() {
  return data.deals.map((deal) => ({ ...deal, ...workspace.deals[deal.id] }));
}
document.querySelectorAll('[data-deal-preview]').forEach((el) => {
  const deal = currentDeals().find(
    (item) => item.id === el.dataset.dealPreview,
  );
  if (deal) el.textContent = deal.stage + ' · Fictional deal';
});
function noteCount(kind, id) {
  return (workspace.notes[kind + ':' + id] || []).length;
}
function initialsBadge(value) {
  return make('span', 'avatar', value);
}
const listApp = document.querySelector('#list-app');
function viewConfig() {
  return {
    entity: listApp.dataset.entity,
    query: document.querySelector('#record-search').value.trim(),
    filter: document.querySelector('#record-filter').value,
    company: document.querySelector('#company-filter')?.value || '',
  };
}
function visibleRecords() {
  if (!listApp) return [];
  const config = viewConfig();
  const records = config.entity === 'people' ? data.people : data.companies;
  return records.filter((record) => {
    const text =
      config.entity === 'people'
        ? `${record.name} ${record.role} ${companyMap.get(record.company).name}`
        : `${record.name} ${record.industry} ${record.location}`;
    return (
      text.toLowerCase().includes(config.query.toLowerCase()) &&
      (!config.filter ||
        (config.entity === 'people' ? record.status : record.industry) ===
          config.filter) &&
      (!config.company || record.company === config.company)
    );
  });
}
function renderList() {
  const records = visibleRecords();
  const entity = listApp.dataset.entity;
  const body = document.querySelector('#record-rows');
  const cards = document.querySelector('#record-cards');
  body.replaceChildren();
  cards.replaceChildren();
  for (const record of records) {
    const tr = document.createElement('tr');
    const name = document.createElement('td');
    const title = recordLink(
      entity === 'people' ? 'person' : 'company',
      record.id,
      record.name,
    );
    name.append(initialsBadge(record.initials), title);
    tr.append(name);
    const values =
      entity === 'people'
        ? [
            record.role,
            companyMap.get(record.company).name,
            record.status,
            String(noteCount('person', record.id)),
          ]
        : [
            record.industry,
            String(record.size),
            record.location,
            String(
              data.people.filter((person) => person.company === record.id)
                .length,
            ),
          ];
    values.forEach((value, index) => {
      const cell = document.createElement('td');
      if (entity === 'people' && index === 1)
        cell.append(recordLink('company', record.company, value));
      else cell.textContent = value;
      tr.append(cell);
    });
    body.append(tr);
    const card = make('article', 'record-card');
    card.append(
      initialsBadge(record.initials),
      recordLink(
        entity === 'people' ? 'person' : 'company',
        record.id,
        record.name,
      ),
    );
    const dl = document.createElement('dl');
    const labels =
      entity === 'people'
        ? ['Role', 'Company', 'Relationship', 'Notes']
        : ['Industry', 'Team size', 'Location', 'People'];
    values.forEach((value, index) => {
      dl.append(make('dt', '', labels[index]), make('dd', '', value));
    });
    card.append(dl);
    cards.append(card);
  }
  document.querySelector('#record-count').textContent =
    `${records.length} ${entity === 'people' ? (records.length === 1 ? 'person' : 'people') : records.length === 1 ? 'company' : 'companies'}`;
  document.querySelector('#empty-records').hidden = records.length > 0;
}
function renderViewOptions() {
  const select = document.querySelector('#saved-view');
  if (!select) return;
  const selected = select.value;
  select.replaceChildren(new Option('Unsaved view', ''));
  for (const view of workspace.views.filter(
    (item) => item.entity === listApp.dataset.entity,
  ))
    select.append(new Option(view.name, view.id));
  select.value = selected;
}
if (listApp) {
  const fields = ['record-search', 'record-filter', 'company-filter'];
  fields.forEach((id) =>
    document
      .getElementById(id)
      ?.addEventListener(id === 'record-search' ? 'input' : 'change', () => {
        document.querySelector('#saved-view').value = '';
        renderList();
      }),
  );
  document.querySelector('#reset-view').addEventListener('click', () => {
    for (const id of fields) {
      const field = document.getElementById(id);
      if (field) field.value = '';
    }
    document.querySelector('#saved-view').value = '';
    renderList();
  });
  document
    .querySelector('#save-view-form')
    .addEventListener('submit', (event) => {
      event.preventDefault();
      const input = document.querySelector('#view-name');
      input.setCustomValidity(input.value.trim() ? '' : 'Name this view.');
      if (!event.currentTarget.reportValidity()) return;
      const config = viewConfig();
      const name = input.value.trim();
      const existing = workspace.views.find(
        (view) =>
          view.entity === config.entity &&
          view.name.toLowerCase() === name.toLowerCase(),
      );
      const view = { ...config, id: existing?.id || crypto.randomUUID(), name };
      workspace.views = existing
        ? workspace.views.map((item) => (item.id === existing.id ? view : item))
        : [...workspace.views, view];
      save('View saved in this browser.');
      renderViewOptions();
      document.querySelector('#saved-view').value = view.id;
    });
  document
    .querySelector('#view-name')
    .addEventListener('input', (event) => event.target.setCustomValidity(''));
  document.querySelector('#saved-view').addEventListener('change', (event) => {
    const view = workspace.views.find((item) => item.id === event.target.value);
    if (!view) return;
    document.querySelector('#record-search').value = view.query;
    const filter = document.querySelector('#record-filter');
    filter.value = [...filter.options].some(
      (option) => option.value === view.filter,
    )
      ? view.filter
      : '';
    if (document.querySelector('#company-filter'))
      document.querySelector('#company-filter').value = view.company;
    document.querySelector('#view-name').value = view.name;
    renderList();
    say('Saved view restored.');
  });
  renderViewOptions();
  renderList();
}
const recordApp = document.querySelector('#record-app');
let record = null;
let recordKey = '';
function renderNotes() {
  const list = document.querySelector('#note-list');
  if (!list) return;
  list.replaceChildren();
  const notes = workspace.notes[recordKey] || [];
  document.querySelector('#empty-notes').hidden = notes.length > 0;
  for (const note of [...notes].reverse()) {
    const item = make('article', 'note-card');
    item.append(
      make('span', 'eyebrow', 'LOCAL NOTE'),
      make('p', '', note.text),
    );
    const date = new Date(note.created);
    if (!Number.isNaN(date.getTime()))
      item.append(
        make(
          'time',
          'note-date',
          date.toLocaleString('en-GB', {
            dateStyle: 'medium',
            timeStyle: 'short',
          }),
        ),
      );
    list.append(item);
  }
}
function dealSummary(deal) {
  const article = make('article', 'linked-deal');
  article.append(
    make('h3', '', deal.name),
    make('p', '', `${deal.stage} · ${money(deal.value)} fictional value`),
  );
  const link = make('a', 'text-link', 'Open pipeline ↗');
  link.href = 'pipeline.html#' + deal.id;
  article.append(link);
  return article;
}
function renderRecord() {
  const kind = recordApp.dataset.kind;
  const records = kind === 'person' ? data.people : data.companies;
  const requested = new URLSearchParams(location.search).get('id');
  record = records.find((item) => item.id === (requested || records[0].id));
  if (!record) {
    document.title = 'Record not found | Circa';
    document.querySelector('#record-title').textContent = 'Record not found';
    document.querySelector('#record-content').hidden = true;
    document.querySelector('#record-missing').hidden = false;
    return;
  }
  recordKey = kind + ':' + record.id;
  document.title = record.name + ' | Circa';
  document.querySelector('#record-title').textContent = record.name;
  document.querySelector('#record-avatar').textContent = record.initials;
  document.querySelector('#record-description').textContent = record.summary;
  document.querySelector('#record-subtitle').textContent =
    kind === 'person' ? record.role : record.industry + ' · Fictional company';
  const facts = document.querySelector('#record-facts');
  const values =
    kind === 'person'
      ? [
          ['Email (fictional)', record.email],
          ['Relationship', record.status],
          ['Location', record.location],
        ]
      : [
          ['Domain (fictional)', record.domain],
          ['Team size', String(record.size)],
          ['Location', record.location],
        ];
  for (const [label, value] of values)
    facts.append(make('dt', '', label), make('dd', '', value));
  const linked = document.querySelector('#linked-records');
  if (kind === 'person') {
    linked.append(
      recordLink(
        'company',
        record.company,
        companyMap.get(record.company).name,
      ),
    );
    document.querySelector('#linked-heading').textContent = 'Connected company';
  } else {
    document.querySelector('#linked-heading').textContent = 'Connected people';
    for (const person of data.people.filter(
      (item) => item.company === record.id,
    )) {
      const row = make('div', 'linked-person');
      row.append(
        recordLink('person', person.id, person.name),
        make('span', '', person.role),
      );
      linked.append(row);
    }
  }
  const relevant = currentDeals().filter((deal) =>
    kind === 'person' ? deal.person === record.id : deal.company === record.id,
  );
  document.querySelector('#linked-deal-count').textContent = String(
    relevant.length,
  );
  document.querySelector('#linked-deal-value').textContent = money(
    relevant.reduce((sum, deal) => sum + deal.value, 0),
  );
  relevant.forEach((deal) =>
    document.querySelector('#linked-deals').append(dealSummary(deal)),
  );
  renderNotes();
  const form = document.querySelector('#note-form');
  form.addEventListener('input', () =>
    document.querySelector('#note-text').setCustomValidity(''),
  );
  form.addEventListener('submit', (event) => {
    event.preventDefault();
    const input = document.querySelector('#note-text');
    input.setCustomValidity(
      input.value.trim() ? '' : 'Write a note before saving.',
    );
    if (!form.reportValidity()) return;
    workspace.notes[recordKey] = [
      ...(workspace.notes[recordKey] || []),
      { text: input.value.trim(), created: new Date().toISOString() },
    ];
    save('Note saved on this record. No message was sent.');
    input.value = '';
    renderNotes();
  });
}
if (recordApp) renderRecord();
const pipeline = document.querySelector('#pipeline');
const dealDialog = document.querySelector('#deal-dialog');
function renderPipeline() {
  if (!pipeline) return;
  const owner = document.querySelector('#owner-filter').value;
  const filtered = currentDeals().filter(
    (deal) => !owner || deal.owner === owner,
  );
  pipeline.replaceChildren();
  for (const stage of data.stages) {
    const items = filtered.filter((deal) => deal.stage === stage);
    const column = make('section', 'stage-column');
    const heading = make('div', 'stage-heading');
    heading.append(
      make('h2', '', stage),
      make('span', '', String(items.length)),
    );
    column.append(
      heading,
      make(
        'p',
        'stage-total',
        money(items.reduce((sum, deal) => sum + deal.value, 0)) + ' demo value',
      ),
    );
    for (const deal of items) {
      const card = make('article', 'deal-card');
      card.id = deal.id;
      card.append(
        make('span', 'eyebrow', companyMap.get(deal.company).name),
        make('h3', '', deal.name),
        make('strong', 'deal-value', money(deal.value)),
        recordLink('person', deal.person, peopleMap.get(deal.person).name),
        make('p', 'deal-owner', 'Owner: ' + deal.owner),
      );
      const edit = make('button', 'text-button', 'Edit deal');
      edit.type = 'button';
      edit.setAttribute('aria-label', 'Edit deal: ' + deal.name);
      edit.addEventListener('click', () => openDeal(deal.id, edit));
      card.append(edit);
      column.append(card);
    }
    if (!items.length)
      column.append(make('p', 'stage-empty', 'No deals in this view.'));
    pipeline.append(column);
  }
  document.querySelector('#pipeline-count').textContent =
    `${filtered.length} deals · ${money(filtered.reduce((sum, deal) => sum + deal.value, 0))} fictional total`;
}
function openDeal(id, button) {
  activeDeal = id;
  dealTrigger = button;
  const deal = currentDeals().find((item) => item.id === id);
  document.querySelector('#deal-name').textContent = deal.name;
  document.querySelector('#deal-context').textContent = deal.context;
  document.querySelector('#deal-stage').value = deal.stage;
  document.querySelector('#deal-value').value = String(deal.value);
  dealDialog.showModal();
}
if (pipeline) {
  document
    .querySelector('#owner-filter')
    .addEventListener('change', renderPipeline);
  document.querySelector('#deal-form').addEventListener('submit', (event) => {
    event.preventDefault();
    const input = document.querySelector('#deal-value');
    const value = Number(input.value);
    input.setCustomValidity(
      Number.isFinite(value) && value >= 0 && value <= 1000000
        ? ''
        : 'Enter a value from 0 to 1,000,000.',
    );
    if (!event.currentTarget.reportValidity()) return;
    workspace.deals[activeDeal] = {
      stage: document.querySelector('#deal-stage').value,
      value,
    };
    save('Deal updated locally. No external CRM was changed.');
    renderPipeline();
    dealDialog.close();
  });
  document
    .querySelector('#deal-value')
    .addEventListener('input', (event) => event.target.setCustomValidity(''));
  document
    .querySelector('#cancel-deal')
    .addEventListener('click', () => dealDialog.close());
  dealDialog.addEventListener('close', () => {
    if (dealTrigger?.isConnected) dealTrigger.focus();
    else document.querySelector('#pipeline-title').focus();
    activeDeal = null;
  });
  renderPipeline();
}
function workspaceJSON() {
  return JSON.stringify(
    {
      context:
        'Circa fictional CRM demonstration. No external accounts or messages.',
      people: data.people,
      companies: data.companies,
      deals: currentDeals(),
      notes: workspace.notes,
      savedViews: workspace.views,
    },
    null,
    2,
  );
}
function shownCSV() {
  const entity = listApp?.dataset.entity || 'deals';
  if (entity === 'people')
    return csvText([
      [
        'Name',
        'Role',
        'Company',
        'Relationship',
        'Fictional email',
        'Latest local note',
      ],
      ...visibleRecords().map((person) => [
        person.name,
        person.role,
        companyMap.get(person.company).name,
        person.status,
        person.email,
        (workspace.notes['person:' + person.id] || []).at(-1)?.text || '',
      ]),
    ]);
  if (entity === 'companies')
    return csvText([
      ['Company', 'Industry', 'Team size', 'Location', 'Latest local note'],
      ...visibleRecords().map((company) => [
        company.name,
        company.industry,
        company.size,
        company.location,
        (workspace.notes['company:' + company.id] || []).at(-1)?.text || '',
      ]),
    ]);
  return csvText([
    ['Deal', 'Company', 'Person', 'Stage', 'Fictional value GBP', 'Owner'],
    ...currentDeals()
      .filter(
        (deal) =>
          !document.querySelector('#owner-filter').value ||
          deal.owner === document.querySelector('#owner-filter').value,
      )
      .map((deal) => [
        deal.name,
        companyMap.get(deal.company).name,
        peopleMap.get(deal.person).name,
        deal.stage,
        deal.value,
        deal.owner,
      ]),
  ]);
}
const exportDialog = document.querySelector('#export-dialog');
let exportTrigger = null;
document.querySelectorAll('[data-export]').forEach((button) =>
  button.addEventListener('click', () => {
    const isCSV = button.dataset.export === 'csv';
    const text = isCSV ? shownCSV() : workspaceJSON();
    if (exportUrl) URL.revokeObjectURL(exportUrl);
    exportUrl = URL.createObjectURL(
      new Blob([text], {
        type: isCSV
          ? 'text/csv;charset=utf-8'
          : 'application/json;charset=utf-8',
      }),
    );
    document.querySelector('#export-title').textContent = isCSV
      ? 'Export the visible records'
      : 'Export the local workspace';
    document.querySelector('#export-text').value = text;
    const link = document.querySelector('#export-download');
    link.href = exportUrl;
    link.download = isCSV
      ? 'circa-visible-records.csv'
      : 'circa-workspace.json';
    document.querySelector('#export-status').textContent = '';
    exportTrigger = button;
    exportDialog.showModal();
  }),
);
document
  .querySelector('#close-export')
  ?.addEventListener('click', () => exportDialog.close());
exportDialog?.addEventListener('close', () => exportTrigger?.focus());
document.querySelector('#copy-export')?.addEventListener('click', async () => {
  try {
    await navigator.clipboard.writeText(
      document.querySelector('#export-text').value,
    );
    document.querySelector('#export-status').textContent = 'Export copied.';
  } catch {
    const text = document.querySelector('#export-text');
    text.focus();
    text.select();
    document.querySelector('#export-status').textContent =
      'Clipboard unavailable. Copy the selected text or download the file.';
  }
});

```
