:root{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#2d3432;background:#f7f7f3;font-synthesis:none;color-scheme:light;--paper:#f7f7f3;--muted:#888e89;--line:#e4e6df;--card-space:0px;--key-edge:0px;--ease:cubic-bezier(.2,.8,.2,1);--move:cubic-bezier(.25,1,.5,1)}
*{box-sizing:border-box}body{margin:0}button,input,textarea{font:inherit}button,a,input,textarea{-webkit-tap-highlight-color:transparent}button{color:inherit;cursor:pointer;border:0;background:none}a{color:inherit;text-decoration:none}button:disabled{opacity:.28;cursor:default}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,summary:focus-visible{outline:2px solid #629f94;outline-offset:4px}[hidden]{display:none!important}svg{width:20px;height:20px;display:block}button{touch-action:manipulation}.app{height:100dvh;min-height:480px;overflow:hidden;position:relative}.app.card-open{--card-space:378px}.app.card-wide{--card-space:490px}
.topbar{height:80px;padding:0 32px;display:flex;align-items:center;gap:18px;position:absolute;top:0;left:0;right:0;z-index:8;background:linear-gradient(var(--paper) 75%,transparent)}.brand{display:flex;align-items:center;gap:10px;font-size:25px;font-weight:650;letter-spacing:-1.1px}.brand img{display:block;height:30px;width:auto}.top-divider{height:21px;width:1px;background:#dedfd8;margin:0 3px 0 8px}.organization{font-size:12px;color:#6f7770;font-weight:500;white-space:nowrap}.search-wrap{position:relative;width:244px;height:38px;display:flex;align-items:center;border:1px solid #e5e7df;background:#ffffffa6;border-radius:12px;box-shadow:0 2px 5px #3c423603}.search-icon{margin:0 9px 0 12px;color:#949b92}.search-icon svg{width:15px;height:15px}.search-wrap input{min-width:0;width:100%;border:0;background:none;outline:none;font-size:12px;color:#444c46;padding:10px 0}.search-wrap input::placeholder{color:#90958c}.search-wrap input:focus-visible{outline:0}.search-wrap:focus-within{border-color:#b1c8bd}.search-wrap kbd{border:1px solid #e2e4db;padding:1px 6px;border-radius:4px;color:#a0a69a;font-size:11px;margin:0 10px}.search-results{position:absolute;top:46px;right:0;width:300px;max-height:340px;overflow:auto;background:#fffffff5;border:1px solid #e5e7df;box-shadow:0 16px 45px #26352a1a;border-radius:15px;padding:6px;backdrop-filter:blur(15px)}.search-result{display:flex;align-items:center;gap:11px;width:100%;text-align:left;padding:10px;border-radius:10px}.search-result:hover,.search-result:focus{background:#f0f3ee}.search-result img{width:32px;height:32px;border-radius:50%;object-fit:cover}.search-result strong{font-size:12px;font-weight:550;display:block}.search-result small{font-size:10px;color:var(--muted)}.empty-result{display:block;padding:15px;font-size:12px;color:var(--muted)}.icon-button{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border-radius:9px;color:#757e75;flex:none}.icon-button:hover:not(:disabled){background:#eaece5;color:#303e35}.icon-button svg{width:17px;height:17px}
.map{position:absolute;left:0;right:var(--card-space);top:60px;bottom:0;overflow:hidden;touch-action:none;cursor:grab;transition:right .52s var(--ease)}.map.dragging{cursor:grabbing}.scene{position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform}.scene>svg{width:100%;height:100%;position:absolute;overflow:visible;pointer-events:none}.ring{fill:none;stroke:#e5e7df;stroke-width:1;stroke-dasharray:2 5}.edge{fill:none;stroke-linecap:round;transition:stroke-opacity .25s,stroke-width .25s}.edge.active{filter:drop-shadow(0 0 3px #ffffff)}.nodes{position:absolute;inset:0;pointer-events:none}.person{position:absolute;width:136px;height:40px;transform:translate(-50%,-50%);padding:0;pointer-events:auto;z-index:2;opacity:var(--node-opacity,1);transition:--node-opacity .25s;isolation:isolate;outline-offset:7px}.node-shell{position:absolute;inset:0;background:#fff;border:1px solid #e1e5dc;border-radius:20px;box-shadow:0 3px 9px #414d3510,0 1px 2px #414d3507;opacity:var(--shell-opacity,1);transition:inset .38s var(--ease),border-radius .38s var(--ease),box-shadow .38s var(--ease),background .25s,--shell-opacity .25s,--p0 .38s var(--move),--p1 .38s var(--move),--p2 .38s var(--move),--pw .38s var(--move),--a0 .38s var(--move),--a1 .38s var(--move),--a2 .38s var(--move),--ring .38s var(--move),--paint .38s var(--move)}.node-label{position:absolute;inset:0;display:flex;justify-content:center;align-items:center;gap:7px;font-size:12px;font-weight:540;letter-spacing:-.15px;white-space:nowrap;transition:transform .38s var(--ease),font-size .38s var(--ease)}.node-dot{width:5px;height:5px;border-radius:50%;background:var(--node-color,#9db9ab);flex:none;transition:opacity .2s}.person[data-affiliation="staff"] .node-shell,.person[data-affiliation="board"] .node-shell{border-color:#d1dacd;background:#fcfdfb}.node-portrait{position:absolute;width:64px;height:64px;left:50%;top:50%;border-radius:50%;object-fit:cover;transform:translate(-50%,-50%) scale(.22);opacity:0;border:3px solid white;box-shadow:0 4px 12px #223e3120;transition:transform .42s var(--ease),opacity .2s;pointer-events:none}.node-title{position:absolute;top:calc(50% + 41px);left:50%;transform:translate(-50%,-6px);width:172px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:10px;color:#879184;opacity:0;transition:opacity .25s,transform .38s;pointer-events:none}.person:hover,.person:focus-visible{z-index:5}.person:hover .node-shell,.person:focus-visible .node-shell{inset:-54px -23px -47px;border-radius:27px;box-shadow:0 12px 30px #33442a1c;border-color:#dce4d5}.person:hover .node-portrait,.person:focus-visible .node-portrait{transform:translate(-50%,-92%) scale(1);opacity:1}.person:hover .node-label,.person:focus-visible .node-label{transform:translateY(35px);font-size:13px}.person:hover .node-dot,.person:focus-visible .node-dot{opacity:0;width:0;margin-right:-7px}.person:hover .node-title,.person:focus-visible .node-title{opacity:1;transform:translate(-50%,0)}.person.on-path .node-shell{border-color:#b9cabc;box-shadow:0 4px 14px #314e3020}.person.center{z-index:3;width:164px!important;height:64px!important}.person.center .node-shell{border:1px solid #b7c7b8;border-radius:22px;background:#fefefc;box-shadow:0 5px 24px #38563812}.person.center .node-label{font-size:12px;font-weight:650;gap:8px}.person.center .node-dot{width:7px;height:7px;background:#647f6c}.person.center.organization-node .node-label{white-space:normal;text-align:left;padding:0 18px;line-height:1.3}.person.center.organization-node .node-dot{background:transparent;width:29px;height:29px;background-image:url('./mark.svg');background-size:contain}.person.organization-node:hover .node-shell,.person.organization-node:focus-visible .node-shell{inset:0;border-radius:22px}.person.organization-node:hover .node-label,.person.organization-node:focus-visible .node-label{transform:none}.person.center.organization-node:hover .node-dot{opacity:1;width:29px;margin-right:0}.loading{position:absolute;inset:0;display:grid;place-items:center;color:#9aa291;font-size:12px;letter-spacing:.04em}
.bottom-controls{position:absolute;bottom:25px;left:32px;right:max(calc(var(--card-space) + 32px),calc(var(--key-edge) + 16px));display:flex;align-items:center;justify-content:flex-end;gap:15px;z-index:6;transition:right .52s var(--ease)}.control-surface{display:flex;align-items:center;background:#ffffffc9;backdrop-filter:blur(18px);border:1px solid #e4e7dd;border-radius:13px;box-shadow:0 4px 15px #263b2507;min-height:42px;padding:5px}.zoom-control{gap:0;flex:none}.zoom-control .icon-button{width:27px;height:28px}.zoom-control .icon-button svg{width:14px;height:14px}
.donor-card{position:absolute;right:20px;top:91px;bottom:25px;width:350px;background:#ffffffed;border:1px solid #e3e7de;border-radius:25px;box-shadow:0 15px 50px #293c2612;backdrop-filter:blur(20px);z-index:7;transform:translateX(calc(100% + 30px));opacity:0;transition:transform .48s var(--ease),opacity .25s,width .45s var(--ease);display:flex;flex-direction:column;overflow:hidden}.card-open .donor-card{transform:translateX(0);opacity:1}.card-wide .donor-card{width:462px}.card-toolbar{padding:18px 20px 0;display:flex;align-items:center;justify-content:space-between}.card-toolbar>div{display:flex;gap:3px}.status-chip{color:#7a8d72;background:#f0f4eb;border:1px solid #e8eddf;padding:5px 9px;border-radius:7px;font-size:9px;text-transform:uppercase;letter-spacing:.09em;font-weight:650}.card-content{padding:20px 24px 25px;overflow:auto;scrollbar-width:thin;scrollbar-color:#dce1d6 transparent;flex:1}.card-identity{padding:0 0 22px;cursor:pointer}.card-portrait{width:78px;height:78px;border-radius:50%;object-fit:cover;box-shadow:0 3px 15px #293f2610;margin-bottom:17px;background:#eff2ea}.card-identity h1{font-size:25px;font-weight:600;letter-spacing:-.85px;line-height:1.15;margin:0 0 7px}.card-role{font-size:12px;color:#7f897c;line-height:1.65}.card-organization{font-size:11px;color:#a0a798;margin-top:2px}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:16px;border-top:1px solid #edf0e7;border-bottom:1px solid #edf0e7;padding:18px 0;margin-bottom:6px}.metric small{font-size:9px;color:#979e8e;display:block;margin-bottom:7px}.metric strong{font-size:22px;letter-spacing:-.65px;font-weight:550}.metric time{font-size:9px;display:block;color:#9ba392;margin-top:5px}.card-section{border-bottom:1px solid #edf0e7}.card-section>summary{display:flex;align-items:center;justify-content:space-between;cursor:pointer;list-style:none;padding:17px 0;font-size:11px;font-weight:600}.card-section>summary::-webkit-details-marker{display:none}.section-heading{display:flex;align-items:center;gap:9px}.section-heading small{font-weight:400;color:#a3aa9a;font-size:10px}.card-section>summary svg{width:13px;height:13px;color:#a4ad9c;transition:transform .2s}.card-section[open]>summary svg{transform:rotate(90deg)}.section-body{padding-bottom:17px}.contact-line{display:flex;align-items:center;gap:10px;font-size:11px;color:#7f8a78;margin:0 0 12px;word-break:break-word}.contact-line:last-child{margin-bottom:0}.contact-line svg{width:14px;height:14px;flex:none;color:#a4ad9c}.notes-input{width:100%;resize:vertical;min-height:91px;max-height:400px;border:1px solid #e8eddf;border-radius:11px;padding:11px;background:#fbfcf8;font-size:11px;line-height:1.7;color:#737e6c}.note-actions{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:8px}.note-status{font-size:9px;color:#a0aa93}.save-note{font-size:10px;font-weight:550;background:#edf2e8;padding:7px 12px;border-radius:8px;color:#607354}.connection-row{display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:9px 3px;border-radius:10px}.connection-row:hover{background:#f4f6ef}.connection-row img{height:30px;width:30px;object-fit:cover;border-radius:50%;background:#e9eee1}.connection-row strong{font-size:11px;display:block;font-weight:550}.connection-row small{font-size:9px;color:#98a08f;display:flex;align-items:center;gap:5px;margin-top:4px}.connection-row i{width:5px;height:5px;border-radius:50%;background:var(--color)}.row-chevron{display:flex;margin-left:auto;color:#b0b6a7}.row-chevron svg{width:13px;height:13px}.no-connections{font-size:11px;color:#9da593}.card-identity.organization-identity .card-portrait{padding:15px;border-radius:22px}
@media(min-width:1500px){.topbar{padding:0 45px}.bottom-controls{left:45px;right:max(calc(var(--card-space) + 45px),calc(var(--key-edge) + 16px))}}
@media(max-width:1250px){.topbar{gap:13px;padding:0 24px}}
@media(max-width:950px){.bottom-controls{bottom:16px;left:22px;right:max(calc(var(--card-space) + 22px),calc(var(--key-edge) + 16px))}.organization{max-width:150px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-wrap{width:200px}.topbar{gap:10px}.top-divider{margin:0 1px}.card-open .search-wrap{width:180px}.card-open .organization{display:none}}
@media(max-width:700px){.app{min-height:460px}.app.card-open,.app.card-wide{--card-space:0px}.topbar{height:70px;padding:0 17px;gap:13px}.brand{font-size:23px;gap:7px}.brand img{height:22px;width:auto}.top-divider,.organization,.search-wrap kbd{display:none}.search-wrap,.card-open .search-wrap{width:165px;height:35px}.search-wrap input{font-size:11px}.map{top:52px;right:0}.bottom-controls{left:15px;right:15px;bottom:15px}.card-open .bottom-controls{transform:translateY(170px)}.card-open .map{bottom:52dvh}.donor-card,.card-wide .donor-card{width:calc(100% - 16px);right:8px;top:auto;bottom:8px;height:49dvh;transform:translateY(calc(100% + 20px));border-radius:25px 25px 18px 18px}.card-open .donor-card{transform:translateY(0)}.card-wide .donor-card{height:calc(100dvh - 85px)}.card-content{padding-top:12px}.card-portrait{width:57px;height:57px;float:left;margin:0 15px 0 0}.card-identity{min-height:75px;padding-bottom:18px}.card-identity h1{font-size:23px}.card-role{font-size:11px}.card-organization{font-size:10px}.card-toolbar{padding-top:12px}.metrics{clear:both}.search-results{width:270px;right:-48px}.card-open .bottom-controls{right:15px}}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:0s!important;transition-duration:0s!important;scroll-behavior:auto!important}.scene{will-change:auto}}

/* Persistent relationship hierarchy. Hover reveals a route without losing the last selection. */
/* Card and outline opacity are set through these numbers and fade through them. A fade of
   opacity itself runs on the GPU and makes each card and outline its own layer; with a
   thousand cards changing at once that froze the page. These fade as the same numbers,
   over the same time, painted in place. */
@property --node-opacity { syntax: '<number>'; inherits: false; initial-value: 1; }
@property --shell-opacity { syntax: '<number>'; inherits: false; initial-value: 1; }
.edge { stroke-opacity: var(--distant-edge-opacity, .10); stroke-width: 1.15; }
.edge.direct { stroke-opacity: .74; stroke-width: 1.55; }
/* Donor views: text beyond direct connections at 75%, pill and bubble shapes at 50% (.75 x .667).
   A hover lifts the shapes on its path to full; the ones it dims keep their share, so a hover
   does not repaint every card in the view (they show at 12% where they were drawn at 18%). */
.person.distant { --node-opacity: var(--distant-node-opacity, .75); }
.scene:not(.tracing) .person.distant:not(.last-path):not(.last-selected) .node-shell,
.scene.tracing .person.distant:not(.on-path):not(.last-path):not(.last-selected) .node-shell { --shell-opacity: var(--distant-shell-opacity, .667); }
.person.last-path { --node-opacity: .9; }
.person.last-selected { --node-opacity: 1; }
.person.last-selected .node-shell {
  border-color: #839e87;
  box-shadow: 0 0 0 3px #cfddc96b, 0 5px 22px #36583c18;
}
.edge.active { stroke-opacity: .9; stroke-width: 2.2; }
.edge.last-picked { stroke-opacity: 1; stroke-width: 3; filter: drop-shadow(0 1px 2px #637b6226); }
.scene.tracing .edge:not(.direct):not(.active):not(.last-picked):not(.revealed):not(.chain) { stroke-opacity: var(--muted-edge-opacity, .055); }
.scene.tracing .edge.last-picked { stroke-opacity: 1; }
/* A hovered person's own lines; lines past the second layer show only then, apart from each
   person's introduction line (app.mjs, deepEdge). */
.edge.revealed { stroke-opacity: 1; }
.scene .edge.deep:not(.revealed):not(.active):not(.last-picked):not(.direct):not(.chain) { stroke-opacity: 0; }
/* The line linking hover clusters' sets, the center's to the first and each to the next (app.mjs,
   chainLit): wide, and glowing in its own color (--glow: line takes the shadow's color from the
   line, keeping its alpha, and --glow-spread blurs a band that many times the line's width rather
   than the line itself; line-canvas.mjs and line-gl.mjs draw it so). */
.edge.chain { stroke-opacity: 1; stroke-width: 4.6; filter: drop-shadow(0 0 9px #00000073); --glow: line; --glow-spread: 3.4; }
.scene.tracing .person:not(.direct):not(.on-path):not(.center):not(.last-path) { --node-opacity: var(--muted-node-opacity, .18); }
.scene.tracing .person.last-path:not(.on-path) { --node-opacity: .9; }
.person.distant.on-path, .person.last-path.on-path { --node-opacity: 1; }

.main-view-button {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 36px; padding: 0 12px; flex: none; white-space: nowrap;
  border: 1px solid #dce3d6; border-radius: 10px; background: #ffffffb8;
  color: #52664f; font-size: 11px; font-weight: 550;
  transition: background .2s, border-color .2s;
}
.main-view-button svg { width: 15px; height: 15px; }
.main-view-button:hover { background: #edf2e8; border-color: #b7c9aa; }
.home-button { width: 36px; padding: 0; }
.scene.overview {
  --distant-node-opacity: .78; --distant-edge-opacity: .42; --distant-shell-opacity: 1;
  --muted-node-opacity: .58; --muted-edge-opacity: .26;
}
.scene.overview .person.close-contact { --node-opacity: 1; }
.scene.overview .edge.direct { stroke-opacity: 1; }
.person.close-contact .node-shell { border: 1.5px solid #81a66b; }

.group-node .node-shell { background: #fdfefb; border-color: #d4dece; box-shadow: 0 3px 0 -1px #eef2e9, 0 4px 0 0 #dce3d5; }
.group-node .node-label { justify-content: flex-start; padding: 0 43px 0 14px; font-size: 10px; }
.group-node .node-dot { display: none; }
/* Name pills carry no dot: only a count (has-count) or the organization's logo sits before a name. */
.person:not(.has-count):not(.organization-node) .node-dot { display: none; }
/* The center's first layer: their tie to the center, smaller and lighter under the name. */
.node-text { display: flex; flex-direction: column; align-items: center; min-width: 0; }
.node-relation { display: none; }
.person.has-relation .node-relation { display: block; margin-top: 1px; font-size: 10px; line-height: 13px; font-weight: 450; letter-spacing: 0; color: #8f988c; }
.person.has-relation .node-shell { border-radius: 24px; }
.group-count { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); font-size: 10px; font-weight: 600; font-variant-numeric: tabular-nums; color: #718666; background: #eef3e8; border-radius: 7px; padding: 4px 5px; }
.person.group-node:hover .node-shell, .person.group-node:focus-visible .node-shell { inset: -2px; border-radius: 22px; border-color: #b1c7a4; }
.person.group-node:hover .node-label, .person.group-node:focus-visible .node-label { transform: none; font-size: 10px; }
.group-identity { padding-bottom: 17px; }
.group-identity h1 { font-size: 23px; }
.group-search { width: 100%; border: 1px solid #e1e7da; border-radius: 10px; background: #f9fbf6; padding: 11px 12px; font-size: 11px; margin-bottom: 13px; }
.group-members { min-height: 45px; }
.group-member { padding: 10px 3px; }
.group-member small { line-height: 1.4; }
.group-more { display: block; margin: 14px auto 0; border: 1px solid #e1e7da; border-radius: 9px; padding: 9px 16px; font-size: 11px; color: #607354; }
.group-more:hover { background: #edf2e8; }
.person { left: 0; top: 0; }
.map-canvas { position: absolute; left: 0; top: 0; pointer-events: none; }
.scene.overview { pointer-events: none; will-change: auto; }
.scene.overview .person:not(.center) .node-shell { box-shadow: 0 2px 7px #414d3508; }
.scene.overview .person:not(.center) .node-dot { width: 4px; height: 4px; }
.scene.overview .person:not(.center):hover .node-label, .scene.overview .person:not(.center):focus-visible .node-label { font-size: 13px; }
.scene.overview .person:not(.center):not(.bubble-node) .node-label { font-size: 12px; }
.scene .person.has-count:not(.center) .node-dot {
  width: var(--dot-size); height: var(--dot-size); display: grid; place-items: center;
  background: color-mix(in srgb, var(--node-color) 24%, #fff); border: 1.5px solid var(--node-color);
  color: #2f4135; font-size: 10px; font-weight: 650; line-height: 1; letter-spacing: -.3px; font-variant-numeric: tabular-nums;
}
.scene .person.has-count:not(.center):hover .node-dot, .scene .person.has-count:not(.center):focus-visible .node-dot { opacity: 1; width: var(--dot-size); margin-right: 0; }
/* Firefox draws a small rounded border in pieces, which a scaled scene can leave seams through (a
   cross in the count): the page's card draws its count as one gradient there. (Only the card in the
   page: the canvas reads the count's look from the border and fill.) */
@supports (-moz-appearance: none) {
  #nodes .person.has-count:not(.center) .node-dot {
    border: 0; border-radius: 0;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--node-color) 24%, #fff) calc(100% - 1.75px), var(--node-color) calc(100% - 1.25px), var(--node-color) calc(100% - .25px), transparent 100%);
  }
}
.bubble-node .node-shell { border-radius: 50%; background: #eef3e8; border: 1px solid #cad8c1; box-shadow: 0 2px 6px #414d350d; }
.person.bubble-node:hover .node-shell, .person.bubble-node:focus-visible .node-shell { inset: -2px; border-radius: 50%; border-color: #9fb991; background: #e5eedd; }
.person.bubble-node.last-selected .node-shell { border-radius: 50%; }
.bubble-count { position: absolute; inset: 0; display: grid; place-items: center; font-size: var(--bubble-font, 10px); font-weight: 620; color: #587050; font-variant-numeric: tabular-nums; letter-spacing: -.02em; pointer-events: none; }
.bubble-name {
  position: absolute; left: 50%; top: calc(100% + 6px); transform-origin: 50% 0; transform: translate(-50%, -4px) scale(var(--inverse-scale, 1));
  white-space: nowrap; font-size: 11px; font-weight: 550; color: #3d4c40; background: #fff; border: 1px solid #dce4d5; border-radius: 9px;
  padding: 5px 9px; box-shadow: 0 8px 22px #33442a1c; opacity: 0; pointer-events: none; transition: opacity .2s, transform .3s var(--ease);
}
.person.bubble-node:hover .bubble-name, .person.bubble-node:focus-visible .bubble-name { opacity: 1; transform: translate(-50%, 0) scale(var(--inverse-scale, 1)); }
/* The zoom's inverse is set on the scene for bubble names and passed down only to them, so a
   new zoom restyles the bubble names instead of every element in the scene. */
@property --inverse-scale { syntax: '<number>'; inherits: false; initial-value: 1; }
.nodes, .bubble-node, .bubble-name { --inverse-scale: inherit; }
/* At rest the scene paints at its current scale so text stays crisp. During a
   zoom or resize it is a single GPU layer that ignores the pointer. */
.scene, .scene.overview { will-change: auto; }
.scene.gesturing { will-change: transform; }
.scene.gesturing .person { pointer-events: none; }
/* Hover cards follow their content: the title sits below the name row, whose
   height (--row) grows with a count bubble, and the card wraps the title with
   fixed padding. Fixed line heights keep the layout independent of the font. */
.person { --row: 16px; }
.scene .person.has-count:not(.center) { --row: calc(var(--dot-size) + 2px); }
.person.has-relation { --row: 30px; }
.scene .person.has-count.has-relation:not(.center) { --row: max(30px, calc(var(--dot-size) + 2px)); }
.node-label { line-height: 16px; }
/* The name row is centred 35px below the pill centre; the title starts 2px under it. */
.node-title { top: calc(50% + 37px + var(--row) / 2); height: 14px; line-height: 14px; }
/* Measured from the pill's center, so pills of any height open alike. */
.person:hover .node-shell, .person:focus-visible .node-shell { inset: calc(50% - 72px) min(-23px, calc(50% - 96px)) calc(50% - 64px - var(--row) / 2); }
/* Portraits and titles are only rendered while a card is open, so hundreds of
   closed pills do not each add paint layers. @starting-style keeps the opening
   animation; allow-discrete keeps them rendered until the closing one ends. */
.node-portrait, .node-title { display: none; transition-behavior: allow-discrete; }
.node-portrait { transition-property: transform, opacity, display; transition-duration: .42s, .2s, .42s; transition-timing-function: var(--ease), ease, linear; }
.node-title { transition-property: opacity, transform, display; transition-duration: .25s, .38s, .38s; transition-timing-function: ease, ease, linear; }
.person:hover :is(.node-portrait, .node-title), .person:focus-visible :is(.node-portrait, .node-title) { display: block; }
@starting-style {
  .person:hover .node-portrait, .person:focus-visible .node-portrait { transform: translate(-50%, -50%) scale(.22); opacity: 0; }
  .person:hover .node-title, .person:focus-visible .node-title { transform: translate(-50%, -6px); opacity: 0; }
}

/* Accounts: sign-in page, account menu, and the Views and Members panels. */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
body.sign-in { min-height: 100dvh; display: grid; place-items: center; background: var(--paper); }
.sign-in-page { width: 100%; padding: 24px; display: grid; place-items: center; margin-top: -6vh; }
.sign-in-form { width: min(100%, 300px); display: flex; flex-direction: column; opacity: 0; transform: translateY(10px); }
.ready .sign-in-form { opacity: 1; transform: none; transition: opacity .6s var(--ease), transform .7s var(--ease); }
.sign-in-brand { justify-content: center; margin: 0 0 30px; }
.sign-in-brand img { height: 36px; }
.sign-in-fields { border: 1px solid #e5e7df; background: #ffffffc9; border-radius: 14px; box-shadow: 0 4px 15px #263b2507; overflow: hidden; transition: border-color .2s, box-shadow .3s; }
.sign-in-fields:focus-within { border-color: #b1c8bd; box-shadow: 0 6px 22px #263b250d; }
.sign-in-fields input { display: block; width: 100%; border: 0; background: none; outline: 0; padding: 14px 16px; font-size: 13px; color: #36403a; }
.sign-in-fields input:focus-visible { outline: 0; }
.sign-in-fields #password { border-top: 1px solid #eef0ea; }
.sign-in-fields input::placeholder { color: #9aa095; }
.sign-in-fields input:read-only { color: #7f897c; }
.sign-in-message { height: 0; margin: 0; overflow: hidden; font-size: 11px; color: #a65a4e; text-align: center; opacity: 0; transition: height .25s var(--ease), margin .25s var(--ease), opacity .2s; }
.sign-in-message.shown { height: 16px; margin-top: 12px; opacity: 1; }
.sign-in-button { margin-top: 14px; height: 42px; border-radius: 12px; background: #edf2e8; color: #52664f; font-size: 12px; font-weight: 600; transition: background .2s, transform .15s, opacity .2s; }
.sign-in-button:hover { background: #e3ebdb; }
.sign-in-button:active { transform: scale(.985); }
.sign-in-form[aria-busy='true'] .sign-in-button { opacity: .55; cursor: default; }
.sign-in-form.leaving { opacity: 0; transform: translateY(-6px) scale(.99); transition: opacity .26s, transform .3s var(--ease); }
.shake { animation: shake .42s cubic-bezier(.36, .07, .19, .97); }
@keyframes shake { 20% { transform: translateX(-7px); } 40% { transform: translateX(6px); } 60% { transform: translateX(-4px); } 80% { transform: translateX(2px); } }

.account-wrap { position: relative; flex: none; }
.account-button { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: #edf2e8; color: #52664f; font-size: 12px; font-weight: 600; transition: background .2s; }
.account-button:hover, .account-button[aria-expanded='true'] { background: #e3ebdb; }
.menu { position: absolute; min-width: 210px; padding: 6px; background: #fffffff5; border: 1px solid #e5e7df; border-radius: 15px; box-shadow: 0 16px 45px #26352a1a; backdrop-filter: blur(15px); animation: menu-in .2s var(--ease); z-index: 9; }
.account-menu { top: 42px; right: 0; transform-origin: top right; }
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px) scale(.96); } }
.account-email { padding: 9px 10px 10px; margin-bottom: 4px; border-bottom: 1px solid #edf0e7; font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.account-menu > button { display: block; width: 100%; padding: 10px; border-radius: 10px; text-align: left; font-size: 12px; color: #3d4640; }
.account-menu > button:hover, .account-menu > button:focus-visible { background: #f0f3ee; }
.card-toolbar > div { margin-left: auto; }

.panel-field { display: flex; align-items: center; gap: 6px; margin-bottom: 13px; padding: 4px 4px 4px 12px; border: 1px solid #e1e7da; border-radius: 10px; background: #f9fbf6; transition: border-color .2s; }
.panel-field:focus-within { border-color: #b1c8bd; }
.panel-field input { flex: 1; min-width: 0; padding: 7px 0; border: 0; background: none; outline: 0; font-size: 11px; color: #4b554e; }
.panel-field input:focus-visible { outline: 0; }
.panel-field input::placeholder { color: #9aa095; }
.panel-field .save-note { flex: none; }
.panel-list { display: flex; flex-direction: column; }
.panel-item, .connection-item { position: relative; transition: height .2s var(--ease), opacity .16s; }
.panel-item.leaving, .connection-item.leaving { opacity: 0; overflow: hidden; }
.panel-item.entering { animation: row-in .34s var(--ease); }
@keyframes row-in { from { opacity: 0; transform: translateY(-5px); } }
div.connection-row:hover { background: none; }
.panel-item .connection-row { padding-right: 36px; }
.member-item .connection-row { min-height: 50px; padding-right: 66px; }
.panel-item > .icon-button, .connection-item > .icon-button, .panel-actions { position: absolute; right: 0; top: 50%; transform: translateY(-50%); }
.panel-actions { display: flex; gap: 2px; }
.panel-item > .icon-button, .connection-item > .icon-button, .panel-actions .icon-button { width: 28px; height: 28px; opacity: 0; transition: opacity .18s, background .2s, color .2s; }
.panel-item > .icon-button svg, .connection-item > .icon-button svg, .panel-actions svg { width: 15px; height: 15px; }
.panel-item:hover > .icon-button, .panel-item:focus-within > .icon-button, .connection-item:hover > .icon-button, .connection-item:focus-within > .icon-button,
.panel-item:hover .panel-actions .icon-button, .panel-item:focus-within .panel-actions .icon-button, .panel-actions .icon-button.confirm, .panel-actions .icon-button.copied { opacity: 1; }
.connection-item .row-chevron { transition: opacity .18s; }
.connection-item:hover .row-chevron, .connection-item:focus-within .row-chevron { opacity: 0; }
.icon-button.confirm { width: auto; padding: 0 10px; font-size: 10px; font-weight: 600; color: #a65a4e; background: #f7ece8; }
.icon-button.confirm:hover:not(:disabled) { color: #93473c; background: #f3e1db; }
.icon-button.copied { color: #52664f; }
.role-select { appearance: none; padding: 0 11px 0 0; border: 0; font: inherit; font-size: 9px; color: #98a08f; cursor: pointer;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2398a08f' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right center / 8px; }
.role-select:focus-visible { outline: 2px solid #629f94; outline-offset: 2px; border-radius: 3px; }
.member-item small > * + *::before { content: '·'; margin-right: 5px; }
@media (hover: none) {
  .panel-item > .icon-button, .connection-item > .icon-button, .panel-actions .icon-button { opacity: 1; }
  .connection-item .row-chevron { opacity: 0; }
}

/* Views menu, minimize line and minimized tray. */
/* Back and the places before it, like breadcrumbs: the latest beside its arrow and earlier ones to
   its left, four as pills and up to three more stacked at the end as faces, as the minimized tray
   stacks them (renderBack in app.mjs). */
.back-wrap { display: flex; align-items: center; gap: 6px; flex: 0 1 auto; min-width: 0; margin-left: auto; }
.back-trail { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; padding: 4px 3px; margin: -4px -3px; }
.back-place { flex: none; display: flex; align-items: center; gap: 7px; height: 32px; max-width: 150px; padding: 0 12px 0 4px; border: 1px solid #dce3d6; border-radius: 16px; background: #ffffffb8;
  color: #52664f; font-size: 11px; font-weight: 550; transition: background .2s, border-color .2s; }
.back-place:hover { background: #edf2e8; border-color: #b7c9aa; }
.back-place:focus-visible { outline-offset: 1px; }
.back-place img { flex: none; width: 24px; height: 24px; border-radius: 50%; object-fit: cover; background: #eff2ea; }
.back-place span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.back-stack { flex: none; display: flex; align-items: center; }
.back-face { position: relative; flex: none; display: block; width: 28px; height: 28px; padding: 0; border: 2px solid #fff; border-radius: 50%; background: #eff2ea; box-shadow: 0 1px 4px #263b2512; transition: transform .2s var(--ease); }
.back-face + .back-face { margin-left: -10px; }
.back-face:hover, .back-face:focus-visible { z-index: 1; transform: translateY(-1px) scale(1.08); }
.back-face img { display: block; width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
/* Back's pseudomorph (launchGhost in app.mjs): the pill on its way down into the center's card. */
.back-ghost { position: fixed; z-index: 30; display: flex; align-items: center; justify-content: center; overflow: hidden; white-space: nowrap; pointer-events: none;
  border: 1px solid #dce3d6; background: #fefefc; letter-spacing: -.15px; }
.back-ghost img { flex: none; border-radius: 50%; object-fit: cover; background: #eff2ea; }
.back-ghost span { overflow: hidden; text-overflow: ellipsis; }
.back-ghost span.wrapped { white-space: normal; text-align: left; line-height: 1.3; }
.menu-divider { height: 1px; margin: 4px 6px; background: #edf0e7; }
.menu-field { padding: 2px; animation: row-in .24s var(--ease); }
.menu-field input { width: 100%; padding: 8px 10px; border: 1px solid #b1c8bd; border-radius: 9px; background: #f9fbf6; outline: 0; font-size: 12px; color: #3d4640; }
.menu-all { display: block; width: 100%; padding: 10px; border-radius: 10px; text-align: left; font-size: 12px; color: #52664f; }
/* The view switcher (app.mjs renderSwitcher): a segmented control centered on the bottom controls'
   line, each view an icon and its name. On a computer the map ends above it (--views-inset), so
   the prospect view's outer ring stays clear of it; on phones it keeps to the left of the zoom
   control, icons only, and scrolls. */
.app { --views-inset: 72px; }
@media (max-width: 950px) { .app { --views-inset: 64px; } }
@media (max-width: 700px) { .app { --views-inset: 0px; } }
.map { bottom: var(--views-inset); }
.view-switcher { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: flex; align-items: center; gap: 2px; padding: 3px; max-width: calc(100% - 220px);
  background: #e9ebe4d9; backdrop-filter: blur(18px); border-radius: 12px; }
.view-item { position: relative; flex: none; transition: opacity .18s, transform .18s var(--ease); }
.view-item.leaving { opacity: 0; transform: scale(.9); }
.view-item.entering { animation: row-in .34s var(--ease); }
.view-option { display: flex; align-items: center; gap: 7px; height: 30px; padding: 0 12px 0 9px; border-radius: 9px; font-size: 12px; font-weight: 550; white-space: nowrap; color: #626b63;
  transition: background .2s, color .2s, box-shadow .2s; }
.view-option:hover { color: #303e35; }
.view-option[aria-pressed='true'] { background: #fff; color: #3f5a3b; box-shadow: 0 1px 3px #263b251c, 0 0 0 .5px #263b2512; }
.view-icon { flex: none; display: grid; place-items: center; }
.view-icon svg { width: 15px; height: 15px; }
.view-switcher.compact .view-option:not([aria-pressed='true']) { padding: 0 9px; }
.view-switcher.compact .view-option:not([aria-pressed='true']) .view-name { display: none; }
.view-switcher.tight { overflow-x: auto; scrollbar-width: none; }
.view-switcher.tight .view-option { padding: 0 9px; }
.view-switcher.tight .view-name { display: none; }
.view-divider { flex: none; width: 1px; height: 16px; margin: 0 6px; background: #d3d7cd; }
.view-save-button.view-option { width: 30px; padding: 0; justify-content: center; color: #858d85; }
.view-delete.icon-button { position: absolute; top: -6px; right: -5px; width: 18px; height: 18px; padding: 0; border-radius: 50%; background: #fff; border: 1px solid #e4e7dd; color: #8a918a;
  box-shadow: 0 2px 6px #263b2512; opacity: 0; transform: scale(.8); transition: opacity .16s, transform .2s var(--ease); }
.view-delete.icon-button svg { flex: none; width: 10px; height: 10px; stroke-width: 2.2; }
.view-delete.icon-button:hover { background: #fff; color: #303e35; }
.view-item:is(:hover, :focus-within) .view-delete { opacity: 1; transform: none; }
@media (hover: none) { .view-item:has(> [aria-pressed='true']) .view-delete { opacity: 1; transform: none; } }
@media (max-width: 700px) {
  .view-switcher { left: 0; transform: translateY(-50%); max-width: calc(100% - 140px); overflow-x: auto; scrollbar-width: none; }
  .view-switcher .view-name { display: none; }
  .view-option { padding: 0 9px; }
  .view-delete.icon-button { top: -3px; right: -3px; }
}
/* Saving a view: its name, and the icon it carries. */
.view-save { position: fixed; width: 244px; padding: 8px; }
.view-save .menu-field { padding: 0 0 8px; }
.view-icons { display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px; }
.view-icon-choice { display: grid; place-items: center; height: 28px; border-radius: 8px; color: #7d857d; transition: background .2s, color .2s; }
.view-icon-choice svg { width: 16px; height: 16px; }
.view-icon-choice:hover { background: #f0f3ee; color: #303e35; }
.view-icon-choice[aria-checked='true'] { background: #edf2e8; color: #3f5a3b; box-shadow: inset 0 0 0 1px #c9dabd; }
.menu-all:hover, .menu-all:focus-visible { background: #f0f3ee; }

/* Minimize sits in the open card's top-right corner, like a window's minimize button. Pointed at,
   it opens into a menu as the marks' does (minimizeCard and hide in app.mjs): minimize stays under
   the pointer, and hide, a crossed-out eye, slides in to its right. A minimized card takes no pointer
   until the pointer has left it. */
.node-minimize { display: none; position: absolute; top: -49px; left: calc(100% - min(-23px, 50% - 96px) - 30px); z-index: 3; align-items: center; gap: 0; padding: 0; border-radius: 15px; cursor: pointer;
  transition: display .38s allow-discrete, left .3s var(--ease), top .3s var(--ease), padding .3s var(--ease), gap .3s var(--ease), background-color .2s, box-shadow .2s, -webkit-backdrop-filter .2s, backdrop-filter .2s; }
.person:is(:hover, :focus-visible):not(.center, .bubble-node) .node-minimize { display: flex; }
.minimize-option { position: relative; flex: none; display: grid; place-items: center; width: 22px; height: 22px; border: 0 solid transparent; border-radius: 50%; color: #9aa295; overflow: hidden;
  transition: width .3s var(--ease), border-width .3s var(--ease), opacity .2s, color .2s, border-color .2s, background-color .2s; }
.minimize-fold::before { content: ''; width: 10px; height: 1.6px; border-radius: 1px; background: currentColor; transform: scaleX(0); transition: transform .4s var(--ease) .08s; }
.person:is(:hover, :focus-visible) .minimize-fold::before { transform: scaleX(1); }
@starting-style { .person:is(:hover, :focus-visible) .minimize-fold::before { transform: scaleX(0); } }
.minimize-hide { width: 0; opacity: 0; }
.minimize-hide svg { flex: none; width: 13px; height: 13px; }
.node-minimize:hover { gap: 4px; padding: 3px; left: calc(100% - min(-23px, 50% - 96px) - 33px); top: -52px; background: #ffffffb8; box-shadow: 0 0 0 1px #e1e5dc, 0 6px 18px #33442a1a; -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
.node-minimize:hover .minimize-option { width: 22px; opacity: 1; border-width: 1.5px; border-color: #e6e9e1; background: #fff; }
.node-minimize .minimize-option:hover { color: #4f6a4a; border-color: #c9dabd; }
.person[data-minimized] { pointer-events: none; }
/* The organization's pill: its logo as a small square mark, a steady label, and room for minimize on the right. */
.person.organization-node:not(.center) .node-dot { width: 12px; height: 12px; border-radius: 0; }
.person.organization-node .node-dot img { display: block; }
.person.organization-node:not(.center):is(:hover, :focus-visible) .node-dot { opacity: 1; width: 12px; margin-right: 0; }
.person.organization-node:not(.center):is(:hover, :focus-visible) .node-label { font-size: 11px; }
.person.organization-node:is(:hover, :focus-visible):not(.center) .node-shell { inset: 0 -26px 0 0; }
.person.organization-node .node-minimize { top: calc(50% - 11px); left: 100%; }
.person.organization-node .node-minimize:hover { top: calc(50% - 14px); left: calc(100% - 3px); }
.scene.gesturing .node-minimize, .scene.moving .node-minimize { display: none; }

.connection-item .connection-row { transition: opacity .25s; }
.connection-item.minimized .connection-row { opacity: .42; }
.connection-item.minimized .row-chevron { opacity: 0; }
.connection-item.minimized > .icon-button { opacity: 1; }

.minimized-tray { position: absolute; right: max(32px, calc(var(--key-edge) - var(--card-space) + 16px)); bottom: calc(77px - var(--views-inset)); z-index: 4; animation: tray-in .4s var(--ease); }
@keyframes tray-in { from { opacity: 0; transform: translateY(8px) scale(.96); } }
.minimized-button { display: flex; align-items: center; gap: 8px; height: 40px; padding: 0 13px 0 7px; background: #ffffffc9; backdrop-filter: blur(18px); border: 1px solid #e4e7dd; border-radius: 13px; box-shadow: 0 4px 15px #263b2507; cursor: pointer; transition: background .2s, box-shadow .25s; }
.minimized-button:hover, .minimized-button[aria-expanded='true'] { background: #fffffff2; box-shadow: 0 6px 20px #263b250f; }
.minimized-faces { display: flex; }
.minimized-faces img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; border: 2px solid #fff; background: #eff2ea; }
.minimized-faces img + img { margin-left: -10px; }
.minimized-count { font-size: 11px; font-weight: 600; color: #6f7770; font-variant-numeric: tabular-nums; }
.minimized-tray.received .minimized-button { animation: received .5s var(--ease) .45s; }
/* The color key (renderKey in app.mjs): a strip down the card's left side, as tall as the card, in
   the card's surface and tucked 26px behind it (the card keeps its rounded corners on top); with the
   card closed, at the window's right edge. Each category of the lines drawn, from the top: a solid
   block with its name under it. */
.color-key { position: absolute; top: 91px; bottom: 25px; right: calc(var(--card-space) - 34px); z-index: 6; width: 102px; padding: 22px 34px 22px 8px;
  display: flex; flex-direction: column; gap: 3px; overflow-y: auto; scrollbar-width: none;
  background: #ffffffed; border: 1px solid #e3e7de; border-radius: 25px 0 0 25px; box-shadow: 0 15px 50px #293c2612; backdrop-filter: blur(20px);
  transition: right .52s var(--ease); animation: key-in .3s var(--ease); }
.app:not(.card-open) .color-key { right: -26px; }
/* How far from the window's right edge the key reaches (its visible 76px), so the zoom control and
   the minimized tray keep 16px clear of it. */
.app.has-key { --key-edge: 76px; }
.app.has-key.card-open { --key-edge: calc(var(--card-space) + 68px); }
@keyframes key-in { from { opacity: 0; } }
/* Pointed at or clicked, a category stays and the others dim, here and on the map (focusKey in app.mjs). */
.key-row { display: flex; flex-direction: column; align-items: center; gap: 5px; flex: none; padding: 6px 2px; border-radius: 12px; font-size: 10px; line-height: 12px; color: #6f7770; text-align: center; transition: opacity .2s, background .2s; }
.key-row:hover, .key-row[aria-pressed='true'] { background: #f0f3ee; }
.key-row.dim { opacity: .38; }
.key-swatch { width: 16px; height: 16px; border-radius: 5px; background: var(--color); }
@media (max-width: 950px) { .card-open .color-key { display: none; } .app.has-key.card-open { --key-edge: 0px; } }
@media (max-width: 700px) { .color-key { display: none; } .app.has-key { --key-edge: 0px; } }
/* Above the zoom control, which floats over the map. */
@media (min-width: 1500px) { .minimized-tray { right: max(45px, calc(var(--key-edge) - var(--card-space) + 16px)); } }
@media (max-width: 950px) { .minimized-tray { right: max(22px, calc(var(--key-edge) - var(--card-space) + 16px)); bottom: calc(68px - var(--views-inset)); } }
@media (max-width: 700px) { .minimized-tray { right: 15px; bottom: 67px; } .card-open .minimized-tray { bottom: 14px; } }
@keyframes received { 45% { transform: scale(1.07); } }
.minimized-menu { bottom: calc(100% + 10px); right: 0; width: 290px; max-height: min(340px, 60vh); overflow: auto; transform-origin: bottom right; animation-name: menu-up; cursor: default; }
@keyframes menu-up { from { opacity: 0; transform: translateY(4px) scale(.96); } }
.minimized-menu .connection-row { padding: 8px; }
.minimize-ghost { position: fixed; left: 0; top: 0; width: 40px; height: 40px; border-radius: 50%; object-fit: cover; border: 2px solid #fff; background: #eff2ea; box-shadow: 0 6px 18px #223e3126; pointer-events: none; z-index: 30; }
/* Phones: the brand and buttons on one row, Back and a full-width search on a second (the row
   break is the bar's ::after). The search's 16px text keeps iOS from zooming the page on focus. */
@media (max-width: 700px) {
  .topbar { flex-wrap: wrap; height: auto; padding: 10px 14px; row-gap: 9px; column-gap: 10px; }
  .topbar::after { content: ''; order: 9; flex-basis: 100%; height: 0; }
  .brand { margin-right: auto; }
  .back-wrap { order: 10; margin-left: 0; max-width: 48%; }
  .search-wrap, .card-open .search-wrap { order: 11; flex: 1 1 0; min-width: 150px; width: auto; height: 40px; }
  .search-wrap input { font-size: 16px; }
  .search-results { left: 0; right: 0; width: auto; top: 48px; max-height: min(420px, 60dvh); }
  .map { top: 100px; }
  .card-wide .donor-card { height: calc(100dvh - 125px); }
  .zoom-control .icon-button { width: 40px; height: 40px; }
  .zoom-control .icon-button svg { width: 16px; height: 16px; }
}
@media (max-width: 400px) { .topbar { padding: 10px 12px; } }
.password-form { display: flex; flex-direction: column; }
.password-form .sign-in-fields input + input { border-top: 1px solid #eef0ea; }
.password-form .sign-in-button:disabled { opacity: .55; }
.sign-in-message.done { color: #52664f; }
/* Switching views fades the map through the page (app.mjs, refresh's cover). */
/* The map fades into the page at its edges (the top bar's gradient adds to it at the top), above
   the cards and lines and below the tray. */
.map::after { content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none; --fade: 32px;
  background: linear-gradient(to bottom, var(--paper), #f7f7f3d9 18%, #f7f7f399 40%, #f7f7f34d 65%, #f7f7f31a 85%, #f7f7f300) top / 100% var(--fade) no-repeat,
    linear-gradient(to top, var(--paper), #f7f7f3d9 18%, #f7f7f399 40%, #f7f7f34d 65%, #f7f7f31a 85%, #f7f7f300) bottom / 100% var(--fade) no-repeat,
    linear-gradient(to right, var(--paper), #f7f7f3d9 18%, #f7f7f399 40%, #f7f7f34d 65%, #f7f7f31a 85%, #f7f7f300) left / var(--fade) 100% no-repeat,
    linear-gradient(to left, var(--paper), #f7f7f3d9 18%, #f7f7f399 40%, #f7f7f34d 65%, #f7f7f31a 85%, #f7f7f300) right / var(--fade) 100% no-repeat; }
@media (max-width: 700px) { .map::after { --fade: 22px; } }
.map-cover { position: absolute; inset: 0; background: var(--paper); opacity: 0; pointer-events: none; transition: opacity .34s var(--ease); }
.map.covered .map-cover { opacity: 1; transition: opacity .2s ease-in; }
@media (prefers-reduced-motion: reduce) { .map-cover, .map.covered .map-cover { transition: none; } }
/* Connection filters (filter-menu.mjs): the top bar's for the view, a card's for its person's own links. */
.filter-wrap { position: relative; flex: none; margin-left: -8px; }
#filters-button.filtered, .card-filter .icon-button.filtered { color: #52664f; background: #edf2e8; }
.filter-menu { top: 42px; right: 0; transform-origin: top right; width: 312px; max-height: calc(100dvh - 96px); overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: #dce1d6 transparent; }
.filter-head, .filter-row { display: grid; grid-template-columns: 1fr 48px 48px; align-items: center; }
.filter-head { padding: 6px 0 2px; font-size: 9px; color: var(--muted); text-align: center; }
.filter-name { display: flex; align-items: center; gap: 10px; min-width: 0; padding: 8px 10px; border-radius: 10px; text-align: left; font-size: 12px; color: #3d4640; transition: background .2s; }
.filter-name span { transition: opacity .2s; }
.filter-row.off .filter-name span { opacity: .45; }
/* What a category's tiers hold, under its name. */
.filter-about { grid-column: 1 / -1; display: grid; gap: 1px; margin: -5px 10px 6px 36px; font-size: 10px; line-height: 14px; color: #9aa196; }
.filter-about b { font-weight: 600; color: #7f887e; }
.filter-row.off .filter-about { opacity: .6; }
.filter-note { margin: 4px 10px 6px; padding-top: 9px; border-top: 1px solid #edf0e7; font-size: 10px; line-height: 14px; color: #9aa196; }
.filter-tier { justify-self: center; width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; }
.filter-name:hover, .filter-name:focus-visible, button.filter-tier:hover, button.filter-tier:focus-visible { background: #f0f3ee; }
/* Checkboxes: empty while off, filled with --fill and marked in --mark while on, a dash while mixed. */
.check { position: relative; flex: none; width: 16px; height: 16px; border-radius: 5px; border: 1.5px solid #cfd4ca; background: #fff; transition: background .18s, border-color .18s; }
button[aria-checked='false']:hover > .check { border-color: #b3bbae; }
.check::after { content: ''; position: absolute; inset: -1.5px; background: var(--mark); -webkit-mask: var(--glyph) center / 12px 12px no-repeat; mask: var(--glyph) center / 12px 12px no-repeat; opacity: 0; transform: scale(.5); transition: opacity .16s, transform .22s var(--ease); }
[aria-checked='true'] > .check, [aria-checked='mixed'] > .check { background: var(--fill); border-color: var(--fill); }
[aria-checked='true'] > .check::after, [aria-checked='mixed'] > .check::after { opacity: 1; transform: none; }
.check { --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.7 6.3l2.2 2.2 4.4-4.9' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
[aria-checked='mixed'] > .check { --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3.2 6h5.6' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E"); }
@media (prefers-reduced-motion: reduce) { .check, .check::after { transition: none; } }
.connections-section { position: relative; }
.card-filter { display: flex; margin: -6px 6px -6px auto; }
.card-filter .icon-button { width: 26px; height: 26px; }
.card-section > summary .card-filter svg, .card-section[open] > summary .card-filter svg { width: 15px; height: 15px; color: inherit; transform: none; }
.card-filter-menu { top: 46px; left: -6px; right: -6px; width: auto; max-height: none; }
/* A card's connections by category (groupConnections in app.mjs): a head with the key's color, the
   category and its count over the people's portraits and names, folding the rows away. A group moved
   to the top slides over the others. */
.connection-group { position: relative; }
.connection-group.sliding { z-index: 1; background: #fff; }
.group-head { display: flex; align-items: center; width: 100%; padding: 8px 3px; border-radius: 10px; text-align: left; font-size: 10px; font-weight: 600; color: #6f7770; transition: background .2s; }
.group-head:hover { background: #f4f6ef; }
.group-swatch { flex: none; width: 10px; height: 10px; margin: 0 20px 0 10px; border-radius: 3px; background: var(--color); }
.group-head small { margin-left: 7px; font-size: 10px; font-weight: 400; color: #a3aa9a; }
.group-chevron { display: flex; margin: 0 3px 0 auto; }
.group-chevron svg { width: 12px; height: 12px; color: #a4ad9c; transform: rotate(90deg); transition: transform .3s var(--ease); }
.connection-group.shut .group-chevron svg { transform: none; }
.group-body { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .34s var(--ease); }
.connection-group.shut .group-body { grid-template-rows: 0fr; }
.group-rows { min-height: 0; overflow: hidden; }
@media (prefers-reduced-motion: reduce) { .group-body, .group-chevron svg { transition: none; } }
/* Card shading (paint.mjs; the render worker draws the same): a conic gradient of the colors of a
   card's links at PAINT_FILL strength, 0 up and clockwise, that becomes a PAINT_BORDER-wide border
   (--ring 1, a cluster's card) as a hole grows from the middle. It eases with the cards' move. */
@property --p0 { syntax: '<color>'; inherits: true; initial-value: transparent; }
@property --p1 { syntax: '<color>'; inherits: true; initial-value: transparent; }
@property --p2 { syntax: '<color>'; inherits: true; initial-value: transparent; }
@property --pw { syntax: '<color>'; inherits: true; initial-value: transparent; }
@property --a0 { syntax: '<angle>'; inherits: true; initial-value: 0rad; }
@property --a1 { syntax: '<angle>'; inherits: true; initial-value: 0rad; }
@property --a2 { syntax: '<angle>'; inherits: true; initial-value: 0rad; }
@property --ring { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --paint { syntax: '<number>'; inherits: true; initial-value: 0; }
.node-shell.painted::before, .node-shell.painted::after { content: ''; position: absolute; border-radius: inherit; pointer-events: none; }
.node-shell.painted::before { inset: -1px; background: conic-gradient(var(--pw) 0turn, var(--p0) var(--a0), var(--p1) var(--a1), var(--p2) var(--a2), var(--pw) 1turn); opacity: calc(var(--paint) * (.2 + .8 * var(--ring))); }
.node-shell.painted::after { inset: 2px; background: inherit; transform: scale(var(--ring)); }
/* Marks (marks.mjs; markMenu in app.mjs): a person is close to the organization (a green heart) or a
   prospect (a gold star). Each mark is a circle (.mark-option). A menu shows the person's own mark,
   or a tag (.mark-blank) when they have none, and opens into a pill of every mark with theirs (or the
   tag) first, so nothing new slides under the pointer. The classes marked, mark-close and
   mark-prospect sit on the card's button or on #card-mark. */
.mark-option { flex: none; display: grid; place-items: center; width: 0; height: 18px; border: 0 solid transparent; border-radius: 50%; background: #fff; color: #b3b9ae; opacity: 0; overflow: hidden;
  transition: width .3s var(--ease), height .38s var(--ease), border-width .3s var(--ease), opacity .2s, color .2s, border-color .2s; }
.mark-option svg { width: 11px; height: 11px; flex: none; transition: width .38s var(--ease), height .38s var(--ease); }
.mark-option[data-mark='prospect'] { --tone: #c9a227; --ring: #ecdfb0; }
.mark-option[data-mark='close'] { --tone: #6f9a5b; --ring: #c9dabd; }
.mark-blank { order: -2; }
.marked .mark-blank { display: none; }
.mark-close [data-mark='close'], .mark-prospect [data-mark='prospect'] { order: -1; }
.mark-close .mark-option[data-mark='close'], .mark-prospect .mark-option[data-mark='prospect'] { width: 18px; opacity: 1; border-width: 1.5px; border-color: var(--ring); color: var(--tone); box-shadow: 0 1px 3px #33442a1a; }

/* On a map card: the circle rests on the pill's top-left corner (the render worker draws it from
   these values, card-probe.mjs) and moves to the open card's top-left corner. Pointing at it there
   opens the menu, icons only; someone with no mark has a faint tag there. */
.node-mark { position: absolute; left: -5px; top: -5px; z-index: 4; display: flex; align-items: center; gap: 0; border-radius: 15px; cursor: pointer;
  transition: left .38s var(--ease), top .38s var(--ease), padding .3s var(--ease), gap .3s var(--ease), background-color .2s, box-shadow .2s, -webkit-backdrop-filter .2s, backdrop-filter .2s; }
.person:is(:hover, :focus-visible):not(.organization-node, .bubble-node, .group-node) .node-mark { left: calc(min(-23px, 50% - 96px) + 9px); top: calc(50% - 63px); }
.person:is(:hover, :focus-visible) .node-mark .mark-option { height: 22px; }
.person:is(:hover, :focus-visible) .node-mark .mark-option svg { width: 13px; height: 13px; }
.person:is(:hover, :focus-visible).mark-close .node-mark [data-mark='close'], .person:is(:hover, :focus-visible).mark-prospect .node-mark [data-mark='prospect'],
.person:is(:hover, :focus-visible):not(.marked) .node-mark .mark-blank { width: 22px; opacity: 1; border-width: 1.5px; }
.person:not(.marked) .node-mark .mark-blank { border-color: #e6e9e1; }
.person .node-mark:is(:hover, .open) { gap: 4px; padding: 3px; background: #ffffffb8; box-shadow: 0 0 0 1px #e1e5dc, 0 6px 18px #33442a1a; -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
.person:is(:hover, :focus-visible):not(.organization-node, .bubble-node, .group-node) .node-mark:is(:hover, .open) { left: calc(min(-23px, 50% - 96px) + 6px); top: calc(50% - 66px); }
.person.organization-node .node-mark:is(:hover, .open) { left: -8px; top: -8px; }
.person .node-mark:is(:hover, .open) .mark-option { width: 22px; height: 22px; opacity: 1; border-width: 1.5px; border-color: #e6e9e1; }
.person.mark-close .node-mark .mark-option[data-mark='close'], .person.mark-prospect .node-mark .mark-option[data-mark='prospect'],
.person .node-mark .mark-option[data-mark]:hover { border-color: var(--ring); color: var(--tone); }
.scene.gesturing .node-mark, .scene.moving .node-mark { pointer-events: none; }

/* On the side card, at the top left: the same circle, a little larger, opening into a lightly
   transparent pill with each mark's name under it. */
.card-mark { position: relative; z-index: 2; display: block; flex: none; width: 26px; height: 26px; margin-right: 8px; }
.mark-pill { position: absolute; left: 0; top: 0; display: flex; align-items: flex-start; padding: 0; border-radius: 20px;
  transition: left .3s var(--ease), top .3s var(--ease), padding .3s var(--ease), background-color .2s, box-shadow .2s, -webkit-backdrop-filter .2s, backdrop-filter .2s; }
.mark-pill .mark-option { height: 26px; }
.mark-pill .mark-option svg { width: 15px; height: 15px; }
.mark-choice { flex: none; display: flex; flex-direction: column; align-items: center; width: 0; padding: 0; overflow: hidden; border-radius: 12px; transition: width .3s var(--ease); }
.mark-choice small { width: 78px; max-height: 0; margin-top: 0; overflow: hidden; font-size: 10px; line-height: 1.3; color: #6f7770; text-align: center; opacity: 0; transition: max-height .3s var(--ease), margin .3s var(--ease), opacity .2s; }
.card-mark.mark-close .mark-choice[data-mark='close'], .card-mark.mark-prospect .mark-choice[data-mark='prospect'],
.card-mark.mark-close .mark-option[data-mark='close'], .card-mark.mark-prospect .mark-option[data-mark='prospect'] { width: 26px; }
.card-mark:not(.marked) .mark-blank { width: 26px; opacity: 1; border-width: 1.5px; border-color: #e1e5dc; }
.card-mark .mark-pill:is(:hover, :has(:focus-visible), .open) { left: -8px; top: -8px; padding: 8px 4px 6px 8px; background: #ffffffb3; box-shadow: 0 0 0 1px #e3e7de, 0 10px 30px #293c261a; -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); }
.card-mark .mark-pill:is(:hover, :has(:focus-visible), .open) .mark-choice { width: 82px; }
.card-mark .mark-pill:is(:hover, :has(:focus-visible), .open) .mark-choice .mark-option { width: 26px; opacity: 1; border-width: 1.5px; border-color: #e6e9e1; }
.card-mark .mark-pill:is(:hover, :has(:focus-visible), .open) .mark-choice small { max-height: 30px; margin-top: 6px; opacity: 1; }
.card-mark .mark-pill:is(:hover, :has(:focus-visible), .open) .mark-blank { margin-right: 2px; }
.card-mark.mark-close .mark-pill .mark-choice[data-mark='close'] .mark-option, .card-mark.mark-prospect .mark-pill .mark-choice[data-mark='prospect'] .mark-option,
.card-mark .mark-pill .mark-choice:is(:hover, :focus-visible) .mark-option { border-color: var(--ring); color: var(--tone); }
.card-mark .mark-choice[aria-pressed='true'] small { color: #3d4640; }
.mark-choice:focus-visible { outline-offset: 0; }
/* Adding a relationship: the blank "+" card in the ring of the card last clicked, and the card it
   opens into where it is (app.mjs, openAdder), kept at screen size. */
.add-node .node-shell, .person.add-node.last-selected .node-shell { border-radius: 20px; background: #fdfdfb; border: 1px solid #e1e5dc; box-shadow: 0 2px 6px #414d350a; }
.person.add-node:hover .node-shell, .person.add-node:focus-visible .node-shell { inset: -3px -4px; border-radius: 23px; border-color: #b1c8bd; background: #fff; box-shadow: 0 8px 20px #33442a14; }
.add-node .bubble-count { font-size: 20px; font-weight: 300; color: #9aa392; letter-spacing: 0; transition: color .2s, transform .3s var(--ease); }
.person.add-node:hover .bubble-count, .person.add-node:focus-visible .bubble-count { color: #4f6a5a; transform: scale(1.12); }
.add-card {
  --inverse-scale: inherit;
  position: absolute; left: 0; top: 0; width: 272px; padding: 8px; z-index: 6; pointer-events: auto; cursor: default;
  transform-origin: 50% 20px; transform: translate3d(var(--x), var(--y), 0) scale(var(--inverse-scale, 1)) translate(-50%, -20px);
  background: #fffffff7; border: 1px solid #e1e5dc; border-radius: 20px; box-shadow: 0 16px 45px #26352a1f; backdrop-filter: blur(15px);
  animation: add-open .36s var(--ease);
}
@keyframes add-open { from { width: 136px; opacity: .4; clip-path: inset(0 0 calc(100% - 40px) 0 round 20px); } }
.add-search { display: flex; align-items: center; height: 38px; padding: 0 10px; border: 1px solid #e5e7df; border-radius: 13px; color: #949b92; }
.add-search:focus-within { border-color: #b1c8bd; }
.add-search svg { width: 15px; height: 15px; flex: none; margin-right: 8px; }
.add-search input, .add-label { min-width: 0; width: 100%; border: 0; background: none; outline: none; font-size: 12px; color: #444c46; }
.add-search input::placeholder, .add-label::placeholder { color: #90958c; }
.add-results { max-height: 300px; overflow: auto; }
.add-results:not(:empty) { margin-top: 6px; }
.add-person { display: flex; align-items: center; gap: 10px; padding: 2px 4px 10px 0; }
.add-person img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
.add-person strong { display: block; font-size: 12px; font-weight: 550; }
.add-person small { font-size: 10px; color: var(--muted); }
.add-tiers { display: flex; gap: 2px; padding: 2px; margin-bottom: 8px; background: #f1f3ee; border-radius: 10px; transition: opacity .2s; }
.add-tiers.off { opacity: .4; pointer-events: none; }
.add-tiers button { flex: 1; height: 26px; border-radius: 8px; font-size: 11px; font-weight: 550; color: #79817a; }
.add-tiers button[aria-pressed="true"] { background: #fff; color: #303e35; box-shadow: 0 1px 3px #33442a1a; }
.add-types { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; margin-bottom: 8px; }
.add-type { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 9px; border-radius: 9px; font-size: 11.5px; color: #444c46; text-align: left; }
.add-type i { width: 8px; height: 8px; border-radius: 50%; background: var(--type-color); flex: none; transition: background .2s; }
.add-type:hover { background: #f0f3ee; }
.add-type[aria-pressed="true"] { background: #eaf0e6; color: #23302a; font-weight: 550; }
.add-label { height: 36px; padding: 0 11px; border: 1px solid #e5e7df; border-radius: 12px; margin-bottom: 8px; }
.add-label:focus { border-color: #b1c8bd; }
.add-save { width: 100%; height: 36px; border-radius: 12px; background: #3f5a4a; color: #fff; font-size: 12px; font-weight: 600; transition: opacity .2s, background .2s; }
.add-save:hover:not(:disabled) { background: #344c3e; }
.add-save:disabled { opacity: .35; cursor: default; }

/* A prospect's routes on the side card, one at a time: name pills stacked from the prospect down to
   the close contact, joined down the portraits by their ties (named beside the line), between arrows
   that page through the routes. The tie's line sits under the portrait's center: 1px border + 6px
   padding + 15px radius, less half the line. */
.route-carousel { display: flex; align-items: center; }
.route-carousel > .icon-button { flex: none; width: 20px; height: 28px; color: #9aa293; }
.route-carousel > .icon-button svg { width: 15px; height: 15px; }
.route-prev svg { transform: rotate(180deg); }
.route-stage { flex: 1; min-width: 0; padding-top: 6px; }
.route-chain { display: flex; flex-direction: column; align-items: center; }
.route-score { font-size: 22px; letter-spacing: -.65px; font-weight: 550; font-variant-numeric: tabular-nums; margin: 0 0 10px; }
.route-tie { display: flex; align-items: stretch; gap: 10px; width: 100%; height: 26px; padding-left: 21px; flex: none; }
.route-tie i { width: 2px; flex: none; background: var(--color); }
.route-tie small { align-self: center; min-width: 0; font-size: 9.5px; color: #98a08f; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.route-pill { position: relative; display: flex; align-items: center; gap: 9px; width: 100%; padding: 6px 14px 6px 6px; text-align: left;
  background: #fff; border: 1px solid #e1e5dc; border-radius: 22px; box-shadow: 0 3px 9px #414d3510, 0 1px 2px #414d3507; transition: border-color .2s, box-shadow .2s; }
.route-pill:disabled { opacity: 1; cursor: default; }
.route-pill:not(:disabled):hover { border-color: #c9d6c6; box-shadow: 0 5px 14px #33442a1c; }
.route-pill img { width: 30px; height: 30px; flex: none; border-radius: 50%; object-fit: cover; background: #e9eee1; }
.route-name { min-width: 0; flex: 1; font-size: 11px; font-weight: 550; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.route-mark { position: absolute; left: -5px; top: -5px; display: grid; place-items: center; width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 1.5px solid var(--ring); color: var(--tone); box-shadow: 0 1px 3px #33442a1a; }
.route-mark[data-mark='prospect'] { --tone: #c9a227; --ring: #ecdfb0; }
.route-mark[data-mark='close'] { --tone: #6f9a5b; --ring: #c9dabd; }
.route-mark svg { width: 9px; height: 9px; }
@media (max-width: 700px) { .route-carousel > .icon-button { width: 24px; height: 44px; } .route-stage { touch-action: pan-y; } }
@media(max-width:420px){.topbar .brand img{height:19px}}
