/* /Components/Layout/BuilderLayout.razor.rz.scp.css */
/*
    The builder's shell: the whole viewport, and nothing else.

    100dvh rather than 100vh -- dvh is the DYNAMIC viewport height, so on a phone the canvas does not
    sit under the browser's collapsing address bar. vh would leave the bottom of the tool permanently
    behind chrome the reader cannot scroll away.
*/

.builder-shell[b-o6zkv704jd] {
    height: 100dvh;
    width: 100%;
    /* overflow: hidden is load-bearing twice over: it stops the canvas scrolling the page, AND it
       clips the nav panel while that panel sits at a negative margin-left waiting to be opened. */
    overflow: hidden;
    display: flex;
    /* ROW, not column (2026-09-08). The nav is the first child and the main pane the second, so
       opening the nav PUSHES the tool right instead of covering it -- which is what was asked for,
       and what the first cut got wrong by making the panel position: fixed. */
    flex-direction: row;
    background: var(--bg, #12151b);
    color: var(--ink, #e8e8ea);
}

/* Everything that is not the nav. Column, because TreeBuild is a bar above a body.
   `min-width: 0` is not optional: a flex item defaults to min-width:auto, so the canvas's own
   content would refuse to shrink and the nav would push the tool off-screen instead of narrowing
   it. */
.builder-main[b-o6zkv704jd] {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/*
    The nav wrapper is a CSS HOOK, not a box.

    display: contents removes it from layout entirely, so NavMenu's own <nav> is a direct child of
    .builder-shell and the collapsed sidebar contributes no height. The class still matches as an
    ancestor in NavMenu.razor.css, which is the whole point of it: Blazor's CSS isolation stops this
    stylesheet at this component's own markup, so the builder presentation of the sidebar has to be
    written in NavMenu.razor.css keyed on this class.
*/
.builder-nav[b-o6zkv704jd] {
    display: contents;
}
/* /Components/Layout/MainLayout.razor.rz.scp.css */
/* "Slate" layout shell - fixed left sidebar + a scrollable main content column,
   replacing "Survey Line"'s sticky top bar (project-knowledge.md "Design system").
   A left sidebar is the single most recognizable "this looks like real SaaS"
   signal (Notion/Linear/Asana/Salesforce all use one) and is a genuine structural
   change, not just new colors on the same top-bar shape, after two top-bar-based
   themes were both rejected. The sidebar's own width/look live in
   NavMenu.razor.css (its own scoped root element) - this file only owns the
   two-column shell and the main content column. */
.app-shell[b-dkkzrfmsjy] {
    display: flex;
    align-items: flex-start;
    min-height: 100vh;
    background: var(--bg);
}

.app-main[b-dkkzrfmsjy] {
    flex: 1 1 auto;
    /* min-width:0 is required on a flex item that contains wide content (tables) -
       without it the item refuses to shrink below its content's intrinsic width
       and can overflow the viewport instead of scrolling internally. */
    min-width: 0;
    width: 100%;
    box-sizing: border-box;
    padding: 2rem 2.5rem 3rem;
    /* Content width cap (2026-07-28 UX polish pass): on ultra-wide monitors the
       pages previously stretched edge-to-edge, putting a row's label and its
       value thousands of pixels apart. Centered in the space remaining next to
       the sidebar (2026-07-31 follow-up: the original left-anchored version
       left the entire right half of an ultra-wide screen empty, which read as
       broken rather than intentional — auto margins on a flex item absorb the
       free space evenly on both sides). */
    max-width: 1360px;
    margin-inline: auto;
}

/* Sidebar stacks above the content on narrow viewports rather than a hamburger
   menu - this app's pages are static SSR with no client-side interactivity to
   drive a collapsible off-canvas panel, so a plain stack is the simplest
   correct behavior. */
@media (max-width: 900px) {
    .app-shell[b-dkkzrfmsjy] {
        flex-direction: column;
    }

    .app-main[b-dkkzrfmsjy] {
        padding: 1.5rem 1.25rem 2rem;
    }
}

/* Footer, bottom of the content column on every page (2026-09-02). Quiet by design: it carries the
   privacy statement and the objection route, which have to be REACHABLE rather than prominent -- a
   loud footer on a family chart is noise, and an unreachable policy is a legal gap. */
.app-footer[b-dkkzrfmsjy] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-top: 3rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
    font-size: .82rem;
    color: var(--ink-soft);
}

.app-footer a[b-dkkzrfmsjy] {
    color: var(--ink-soft);
    text-decoration: none;
}

/* Underline on hover AND on focus: the link is faint, so the pointer and the keyboard both need the
   same confirmation that it is a link. */
.app-footer a:hover[b-dkkzrfmsjy],
.app-footer a:focus-visible[b-dkkzrfmsjy] {
    color: var(--ink);
    text-decoration: underline;
}

.app-footer-sep[b-dkkzrfmsjy] {
    color: var(--ink-faint);
}


/* ==== The confirmation dialog (2026-09-08) ====
   A native <dialog>: showModal() centres it and paints ::backdrop, so there is no positioning code
   and no focus trap to write. Sized in rem so it stays readable at any a11y font scale, and capped
   at 92vw so it never overflows a phone. */
.confirm-dialog[b-dkkzrfmsjy] {
    width: min(26rem, 92vw);
    padding: 1.25rem;
    border: 1px solid var(--line, rgba(128, 128, 128, .35));
    border-radius: .6rem;
    background: var(--panel, #ffffff);
    color: var(--ink, #e8e8ea);
    box-shadow: 0 1.2rem 3rem rgba(0, 0, 0, .5);
}

.confirm-dialog[b-dkkzrfmsjy]::backdrop {
    background: rgba(0, 0, 0, .55);
}

.confirm-dialog-title[b-dkkzrfmsjy] {
    margin: 0 0 .5rem;
    font-size: 1.05rem;
}

.confirm-dialog-message[b-dkkzrfmsjy] {
    margin: 0 0 1.1rem;
    color: var(--ink-soft, #b9b9c0);
}

.confirm-dialog-actions[b-dkkzrfmsjy] {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
}

/* One short fade, and only where motion is welcome. A dialog that animates in slowly is a dialog
   people click through before it settles. */
@media (prefers-reduced-motion: no-preference) {
    .confirm-dialog[open][b-dkkzrfmsjy] {
        animation: confirm-dialog-in-b-dkkzrfmsjy .14s ease-out;
    }
}

@keyframes confirm-dialog-in-b-dkkzrfmsjy {
    from { opacity: 0; transform: translateY(-.4rem); }
    to   { opacity: 1; transform: none; }
}
/* /Components/Layout/NavMenu.razor.rz.scp.css */
/* "Slate" sidebar - fixed left column, replacing "Survey Line"'s sticky ink top
   bar (project-knowledge.md "Design system"). ~240px, sticky so it stays in view
   while the (possibly long) main content column scrolls. */
.sidebar[b-rt16f9be07] {
    flex: none;
    width: 240px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    padding: 1.25rem 1rem;
    /* Deep teal, not --bg-subtle gray - 2026-07-24 color pass. The colored
       sidebar block against the (now tinted) lighter content area is the
       single biggest lever against the "too much white" feeling; the strong
       color contrast against the main content also replaces the need for a
       separating border-right. */
    background: var(--sidebar-bg);
    position: sticky;
    top: 0;
    align-self: flex-start;
    min-height: 100vh;
}

@media (max-width: 900px) {
    .sidebar[b-rt16f9be07] {
        width: 100%;
        min-height: auto;
        position: static;
        border-bottom: 1px solid var(--sidebar-border);
    }
}

.sidebar-brand[b-rt16f9be07] {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding-bottom: .9rem;
    margin-bottom: .5rem;
    border-bottom: 1px solid var(--sidebar-border);
}

.sidebar-brand:hover[b-rt16f9be07] {
    text-decoration: none;
}

/* Small colored rounded-square with initials - no crosshair glyph, no image.
   White chip + teal letters (was solid-accent + white) - 2026-07-24: two close
   teals stacked (this mark's old --accent fill on the new --sidebar-bg) read
   as low-contrast/muddy, so a solid white chip is used instead, guaranteed to
   read clearly regardless of the exact sidebar shade.
   Dark-mode round (2026-08), Lighthouse-caught: color was var(--accent), which is fine paired
   with this fixed white background in LIGHT mode (~5.2:1) but fails badly in DARK mode
   (~1.9:1) once --accent flips to the bright mint dark token - this chip's white background is
   deliberately theme-INVARIANT (the sidebar is always dark regardless of app theme, per the
   comment above), so its text needs to be theme-invariant too, not tied to a token that flips.
   var(--sidebar-bg) (#0d4941, itself already fixed regardless of app theme) gives ~5.5:1 on
   white and reads as the mark done in the sidebar's own brand teal either way. */
.sidebar-brand-mark[b-rt16f9be07] {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 7px;
    background: #ffffff;
    color: var(--sidebar-bg);
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-weight: 700;
    font-size: .7rem;
}

.sidebar-brand-name[b-rt16f9be07] {
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1rem;
    color: var(--sidebar-ink);
}

/* Command palette hint button (Ctrl+K, Wave 4) — quiet styling, sits above the tree
   switcher/nav list. Literal markup child of this component (not a child component's output),
   so it doesn't need ::deep. */
.sidebar-palette-hint[b-rt16f9be07] {
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
    margin-bottom: .75rem;
    padding: .45rem .6rem;
    background: none;
    border: 1px solid var(--sidebar-border);
    border-radius: var(--radius-sm);
    color: var(--sidebar-ink-soft);
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .8rem;
    text-align: left;
    cursor: pointer;
}

.sidebar-palette-hint:hover[b-rt16f9be07] {
    color: var(--sidebar-ink);
    background: var(--sidebar-hover-bg);
}

.sidebar-palette-hint-label[b-rt16f9be07] {
    flex: 1 1 auto;
}

.sidebar-palette-kbd[b-rt16f9be07] {
    flex: none;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .68rem;
    color: var(--sidebar-ink-faint);
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid var(--sidebar-border);
    border-radius: 4px;
    padding: .1rem .35rem;
}

[b-rt16f9be07] .sidebar-tree {
    padding: .1rem .35rem 1rem;
    margin-bottom: .5rem;
    border-bottom: 1px solid var(--sidebar-border);
}

/* The switcher's caption (2026-08-28). Without it the control was a bold tree name sitting under the
   brand, which reads as a heading telling you where you are -- people did not discover it was a
   control at all. Small caps above it, in the sidebar's muted ink, so it labels without competing
   with the name underneath. ::deep like every rule here: the sidebar markup sits inside
   .sidebar-collapsible, which is a child element, so scoped selectors alone do not reach it. */
[b-rt16f9be07] .sidebar-tree-label {
    display: block;
    margin-bottom: .2rem;
    color: var(--sidebar-ink-soft, var(--sidebar-ink));
    font-size: .6875rem;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    opacity: .7;
}

[b-rt16f9be07] .sidebar-tree-switcher {
    margin-bottom: .35rem;
}

/* The no-JS submit beside the switcher (2026-08-28). Full width under the select rather than beside
   it: the sidebar is ~240px and a select plus a button on one row leaves the tree name unreadable,
   which would trade a working control for a legible one. Pale brass on the dark panel, matching
   .sidebar-tree-new next to it -- @sidebar-mint exists because @accent has too little contrast here. */
[b-rt16f9be07] .sidebar-tree-go {
    display: block;
    width: 100%;
    margin-top: .3rem;
    padding: .25rem .4rem;
    background: transparent;
    color: var(--sidebar-mint);
    border: 1px solid var(--sidebar-border);
    border-radius: 5px;
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .75rem;
    font-weight: 600;
    cursor: pointer;
}

[b-rt16f9be07] .sidebar-tree-go:hover,
[b-rt16f9be07] .sidebar-tree-go:focus-visible {
    background: var(--sidebar-hover-bg);
}

/* Tree-switcher dropdown (Phase 5) - single <select>, auto-submits on change, replacing
   the old always-visible link-per-other-membership list. Disabled (not hidden) when there's
   only one option, so the active tree name stays visible either way - this is also what
   keeps a platform admin's virtual/foreign entry visible (Phase 4 review finding: the old
   active-tree label went missing entirely for a membership with no real TreeUser row). */
[b-rt16f9be07] .sidebar-tree-select {
    width: 100%;
    background: var(--sidebar-bg);
    color: var(--sidebar-ink);
    border: 1px solid var(--sidebar-border);
    border-radius: 5px;
    padding: .3rem .4rem;
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-weight: 600;
    font-size: .85rem;
    cursor: pointer;
}

[b-rt16f9be07] .sidebar-tree-select:disabled {
    cursor: default;
    opacity: 1;
    -webkit-text-fill-color: var(--sidebar-ink);
    border-color: transparent;
    padding-left: 0;
}

/* The popup list needs BOTH a colour and a background. This rule set only `color: #000000`, and a
   native select popup takes its background from the select itself - which is the dark sidebar surface.
   Black on near-black: every option except the highlighted one was invisible. It was already marginal
   against the old teal sidebar and became unreadable when "Iron gall" made that surface darker, so
   this is half a pre-existing bug and half a regression I introduced.

   Tokens rather than fixed white/black, so the popup follows the app theme: a light panel with ink
   text in light mode, a dark panel with light text in dark mode. Both are legible; a hard-coded white
   popup would be legible too but would flash white in dark mode. */
[b-rt16f9be07] .sidebar-tree-select option {
    background-color: var(--panel);
    color: var(--ink);
}

[b-rt16f9be07] .sidebar-tree-new {
    display: block;
    margin-top: .3rem;
    font-size: .8rem;
    color: var(--sidebar-mint);
}

[b-rt16f9be07] .sidebar-tree-new:hover {
    color: #ffffff;
    text-decoration: underline;
}

[b-rt16f9be07] .sidebar-nav {
    margin: 0;
    padding: 0;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
}

/* The links inside one group. This is the <ul> the old .sidebar-nav was -- the outer element is now
   a plain column of <details> sections, and the gap belongs between LINKS, not between groups
   (each group already has its own rule line and caption padding). */
[b-rt16f9be07] .sidebar-nav-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* ---------------------------------------------------------------------------------------------
   COLLAPSIBLE GROUPS (2026-09-14). <details>/<summary>, so it works with no JavaScript at all --
   see the comment in NavMenu.razor for why that constraint is not negotiable here.
   --------------------------------------------------------------------------------------------- */
[b-rt16f9be07] .sidebar-nav-section {
    display: block;
}

/* The default disclosure triangle, removed twice: `list-style` covers Firefox and standards-track
   Chrome, the pseudo-element covers older WebKit. A <summary> with no list-style-type still shows the
   marker in Safari without the second rule. */
[b-rt16f9be07] .sidebar-nav-group {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: .4rem;
    user-select: none;
}

[b-rt16f9be07] .sidebar-nav-group::-webkit-details-marker {
    display: none;
}

/* THE CHEVRON IS DRAWN, NOT AN ICON. Font Awesome here is purged by tools/purge-vendor-css.mjs and
   carries only the glyphs this app already renders -- no chevron or angle is among them, and adding
   one means a new vendor build for a 6px triangle. Two borders rotated 45 degrees cost nothing and
   cannot fall out of a purge.

   ::after, because ::before is already the group's coloured rule line. */
[b-rt16f9be07] .sidebar-nav-group::after {
    content: "";
    width: .38rem;
    height: .38rem;
    margin-left: auto;
    margin-right: .15rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    opacity: .7;
    transition: transform .15s ease;
}

/* Open points down, closed points right -- the disclosure convention every file tree and accordion
   already taught the reader, and the one direction that does not have to be learned here. */
[b-rt16f9be07] .sidebar-nav-section:not([open]) > .sidebar-nav-group::after {
    transform: rotate(-45deg);
}

[b-rt16f9be07] .sidebar-nav-group {
    transition: color .14s ease, background-color .14s ease;
}

/* ---------------------------------------------------------------------------------------------
   THE OPEN/CLOSE ANIMATION (2026-09-17). "Dodaj lijepu animaciju kako se spusta i dize."

   A <details> has no animation of its own: the content is there or it is not, and the browser jumps
   between the two. What makes this animatable with NO JavaScript is the pair below --
   `::details-content`, which is the box wrapping everything after the <summary>, and
   `interpolate-size: allow-keywords`, which is what lets a transition run to `auto`. Without the
   second one `block-size: auto` is not an interpolable value and the transition silently does
   nothing, which is exactly how this reads as "the animation did not work".

   interpolate-size INHERITS, so it is set on the nav container rather than on :root -- a scoped
   stylesheet cannot reach :root, and putting it in app.less would move half of one rule into a file
   that needs the purge to reach a browser.

   Browsers without ::details-content skip the whole block and the groups open instantly, exactly as
   they did before. Nothing is load-bearing here. *
   --------------------------------------------------------------------------------------------- */
[b-rt16f9be07] .sidebar-nav {
    interpolate-size: allow-keywords;
}

[b-rt16f9be07] .sidebar-nav-section::details-content {
    block-size: 0;
    overflow: hidden;
    /* content-visibility is what the browser flips to hide the content, and it is a DISCRETE
       property -- without allow-discrete it snaps at the start of the close and the links vanish
       before the box has finished collapsing. */
    transition: block-size .22s cubic-bezier(.22, .61, .36, 1), content-visibility .22s allow-discrete;
}

[b-rt16f9be07] .sidebar-nav-section[open]::details-content {
    block-size: auto;
}

/* Restoring a remembered group on page load must not animate -- see js/nav-groups.js. */
[b-rt16f9be07] .sidebar-nav-section[data-nav-restoring]::details-content {
    transition: none;
}

/* The caption is the click target for the whole group, so it needs a visible keyboard focus of its
   own -- <summary> is focusable and the sidebar's ring is defined for links, not for this. */
[b-rt16f9be07] .sidebar-nav-group:focus-visible {
    outline: 2px solid var(--sidebar-mint);
    outline-offset: -2px;
    border-radius: var(--radius-sm);
}

[b-rt16f9be07] .sidebar-nav-group {
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--sidebar-ink-soft);
    opacity: .65;
    padding: .85rem .65rem .3rem;
}

[b-rt16f9be07] .sidebar-nav-section:first-child > .sidebar-nav-group {
    padding-top: .55rem;
}

/* THE GROUP RULE (2026-08-28). A 2px line across the top of each group, in the group's own colour,
   with the caption sitting just under it -- the top edge of an invisible rectangle holding those
   links, which is exactly how it was asked for.

   ::before rather than border-top on the <li>, because the caption has left padding to line up with
   the link labels and the RULE must not: a line that starts where the text starts reads as an
   underline for the word above it, not as the top of a block. So the pseudo-element is pulled out to
   the full width of the sidebar's padding box.

   Colour comes from a per-group custom property, so each group is one declaration and the palette
   stays in one place. --sidebar-ink-faint for settings: not a colour, and deliberately so. */
[b-rt16f9be07] .sidebar-nav-group {
    position: relative;
}

[b-rt16f9be07] .sidebar-nav-group::before {
    content: "";
    position: absolute;
    top: 0;
    left: .35rem;
    right: .35rem;
    height: 2px;
    border-radius: 2px;
    background: var(--group-rule, var(--sidebar-border));
    opacity: .9;
}

/* The first group's rule sits under the switcher's own divider, so it would be two horizontal lines
   a few pixels apart. Its caption keeps the colour; the line does not repeat it. */
[b-rt16f9be07] .sidebar-nav-section:first-child > .sidebar-nav-group::before {
    display: none;
}

[b-rt16f9be07] .sidebar-nav-group.is-trees    { --group-rule: var(--kin); }
[b-rt16f9be07] .sidebar-nav-group.is-family   { --group-rule: var(--life); }
[b-rt16f9be07] .sidebar-nav-group.is-records  { --group-rule: var(--accent); }
[b-rt16f9be07] .sidebar-nav-group.is-settings { --group-rule: var(--sidebar-ink-faint); }

/* The caption takes the group's colour too, at full opacity where the shared rule above sets .65 --
   a coloured line above grey text reads as a stray divider rather than as a heading. Brass and sage
   are both tuned as dark ink in the light palette, so the SIDEBAR versions are used: this panel is
   near-black in either theme. */
[b-rt16f9be07] .sidebar-nav-group.is-trees,
[b-rt16f9be07] .sidebar-nav-group.is-family,
[b-rt16f9be07] .sidebar-nav-group.is-records {
    opacity: 1;
    color: var(--group-caption, var(--sidebar-ink-soft));
}

[b-rt16f9be07] .sidebar-nav-group.is-trees   { --group-caption: #8fa8d4; }  /* @d-kin  */
[b-rt16f9be07] .sidebar-nav-group.is-family  { --group-caption: #8fbf7a; }  /* @d-life */
[b-rt16f9be07] .sidebar-nav-group.is-records { --group-caption: var(--sidebar-mint); }

/* HOVER LIGHTENS THE CAPTION'S OWN COLOUR (founder, 2026-09-17: "na hover dodaj od boje teksta
   (svjetliju varijantu da se vidi tekst)"). It used to set opacity: 1 and nothing else, which is
   invisible on three of the four groups -- those already sit at full opacity because they carry a
   colour, so hovering a coloured caption did literally nothing. Only Settings, the one grey group,
   changed at all.

   color-mix against the group's OWN --group-caption rather than a hardcoded pale grey: each caption
   lightens along its own hue, so the group still reads as indigo / sage / brass when the pointer is
   on it. The fallback inside var() covers Settings, which deliberately has no colour.

   A faint band in the group's rule colour goes with it, because the whole caption row is the click
   target and a colour change on six small uppercase letters is easy to miss.

   IT HAS TO SIT AFTER THE THREE COLOUR RULES ABOVE, and that cost a measured round trip: written
   further up the file it set the colour and NOTHING CHANGED on hover (rgb(143,191,122) before and
   after, read off the computed style in a browser). `.sidebar-nav-group.is-family` is two classes
   and `.sidebar-nav-group:hover` is a class plus a pseudo-class -- the same specificity once Blazor
   adds its scope attribute to both -- so the later rule simply won. Source order is the whole fix;
   raising the specificity instead would mean repeating the hover rule once per group. */
[b-rt16f9be07] .sidebar-nav-group:hover {
    opacity: 1;
    color: color-mix(in oklab, var(--group-caption, var(--sidebar-ink-soft)) 62%, white);
    background: color-mix(in oklab, var(--group-rule, var(--sidebar-border)) 16%, transparent);
    border-radius: var(--radius-sm);
}

/* The chevron rides the same lightening, since it inherits currentColor, and stops being the one
   muted thing on a row that just brightened. */
[b-rt16f9be07] .sidebar-nav-group:hover::after {
    opacity: 1;
}

[b-rt16f9be07] .nav-link {
    display: flex;
    align-items: center;
    gap: .65rem;
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .875rem;
    font-weight: 500;
    color: var(--sidebar-ink-soft) !important;
    padding: .5rem .65rem;
    border-radius: var(--radius-sm);
    transition: background-color 120ms ease, color 120ms ease;
}

[b-rt16f9be07] .nav-link i {
    width: 16px;
    flex: none;
    text-align: center;
    font-size: .85em;
    color: var(--sidebar-ink-soft);
}

[b-rt16f9be07] .nav-link:hover {
    color: var(--sidebar-ink) !important;
    background: var(--sidebar-hover-bg);
}

[b-rt16f9be07] .nav-link:hover i {
    color: var(--sidebar-ink);
}

/* Active chip deliberately does NOT reuse --accent on this dark sidebar bg
   (two close teals stacked read as low-contrast) - a translucent white chip
   plus solid-white text/icon instead, so "active" stays unmistakable. */
[b-rt16f9be07] .nav-link.active {
    color: #ffffff !important;
    background: var(--sidebar-active-bg);
    font-weight: 600;
}

[b-rt16f9be07] .nav-link.active i {
    color: #ffffff;
}

.sidebar-footer[b-rt16f9be07] {
    margin-top: 1rem;
    padding-top: .9rem;
    border-top: 1px solid var(--sidebar-border);
}

/* Wraps <ThemeToggle /> - literal markup child of THIS component (not projected content from
   ThemeToggle's own template), so it doesn't need ::deep. */
.sidebar-theme-row[b-rt16f9be07] {
    margin-bottom: .6rem;
}

.sidebar-culture[b-rt16f9be07] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .3rem;
    padding: 0 .1rem;
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .75rem;
    margin-bottom: .6rem;
}

.sidebar-culture-link[b-rt16f9be07] {
    color: var(--sidebar-ink-soft);
}

.sidebar-culture-link:hover[b-rt16f9be07] {
    color: var(--sidebar-mint);
    text-decoration: underline;
}

.sidebar-culture-current[b-rt16f9be07] {
    color: var(--sidebar-ink);
    font-weight: 600;
}

.sidebar-culture-sep[b-rt16f9be07] {
    color: var(--sidebar-ink-faint);
}

[b-rt16f9be07] .sidebar-logout-btn {
    display: flex;
    align-items: center;
    gap: .55rem;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    padding: .45rem .5rem;
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .85rem;
    font-weight: 500;
    color: var(--sidebar-ink-soft);
    border-radius: var(--radius-sm);
}

[b-rt16f9be07] .sidebar-logout-btn:hover {
    background: var(--sidebar-hover-bg);
    color: var(--sidebar-ink);
}

/* ==== Mobile collapsible nav (A1, 2026-08 mobile pass) ====
   Pure-CSS checkbox+label toggle - no JS, works with JS disabled (nav simply starts collapsed
   on mobile either way, same as it always renders regardless of script execution). Everything
   below is either hidden unconditionally (so desktop is unaffected without needing every rule
   inside the media query) or explicitly scoped to ≤900px. Desktop's .sidebar-brand and the
   AuthorizeView-driven nav/tree/footer keep their original DOM position and CSS untouched.
   NOTE: this block is placed LAST in the file deliberately - its .sidebar-brand / .sidebar-nav
   etc. overrides share equal specificity with the base rules earlier in this file, so source
   order is what makes them win; moving this block earlier would silently break the override. */
.sidebar-mobile-toggle[b-rt16f9be07] {
    /* Hidden at all widths (no visible checkbox UI) - the associated <label for> still toggles
       it while display:none, standard cross-browser behavior for this pattern. */
    display: none;
}

.sidebar-topbar[b-rt16f9be07] {
    /* Desktop: not shown - the real .sidebar-brand above is used instead. */
    display: none;
}

/* display:contents on desktop = zero layout effect: its children (palette hint, tree
   switcher, nav list, footer) remain direct flex items of .sidebar exactly as before this
   wrapper was introduced. Only becomes a real (hideable) flex container at ≤900px below. */
.sidebar-collapsible[b-rt16f9be07] {
    display: contents;
}

@media (max-width: 900px) {
    .sidebar-topbar[b-rt16f9be07] {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: .5rem;
        padding-bottom: .75rem;
        margin-bottom: .75rem;
        border-bottom: 1px solid var(--sidebar-border);
    }

    /* Replaced by the compact brand inside .sidebar-topbar above. */
    .sidebar-brand[b-rt16f9be07] {
        display: none;
    }

    .sidebar-topbar-brand[b-rt16f9be07] {
        display: flex;
        align-items: center;
        gap: .6rem;
        min-width: 0;
    }

    .sidebar-topbar-actions[b-rt16f9be07] {
        display: flex;
        align-items: center;
        flex: none;
    }

    .sidebar-topbar-btn[b-rt16f9be07] {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        height: 44px;
        flex: none;
        background: none;
        border: 0;
        border-radius: var(--radius-sm);
        color: var(--sidebar-ink);
        font-size: 1.05rem;
        cursor: pointer;
    }

    .sidebar-topbar-btn:hover[b-rt16f9be07] {
        background: var(--sidebar-hover-bg);
    }

    /* Hamburger <-> close icon swap, driven by the checkbox's :checked state (no JS). */
    .sidebar-toggle-icon-open[b-rt16f9be07] {
        display: none;
    }

    .sidebar-mobile-toggle:checked ~ .sidebar-topbar .sidebar-toggle-icon-closed[b-rt16f9be07] {
        display: none;
    }

    .sidebar-mobile-toggle:checked ~ .sidebar-topbar .sidebar-toggle-icon-open[b-rt16f9be07] {
        display: inline;
    }

    /* Collapsed by default; expands when the checkbox (toggled by the hamburger label) is
       checked. Tree switcher, full nav list, and culture/logout footer all live in here,
       so they stay reachable once expanded. */
    .sidebar-collapsible[b-rt16f9be07] {
        display: none;
    }

    .sidebar-mobile-toggle:checked ~ .sidebar-collapsible[b-rt16f9be07] {
        display: flex;
        flex-direction: column;
    }

    /* ---- Touch targets (A3): larger padding on mobile only, desktop untouched ---- */
    .sidebar-palette-hint[b-rt16f9be07] {
        padding: .7rem .6rem;
    }

    [b-rt16f9be07] .nav-link {
        padding: .75rem .65rem;
    }

    [b-rt16f9be07] .sidebar-logout-btn {
        padding: .7rem .5rem;
    }

    [b-rt16f9be07] .sidebar-tree-select {
        padding: .55rem .4rem;
    }
}

/* ==== The BUILDER's burger: it PUSHES, it animates both ways, and an arrow closes it ====
   (2026-09-08, second pass.)

   Founder: "Ovaj navbar na canvasu pokriva sve komponente ... zelim da umjesto da pokriva
   komponente, da samo gurne desno. Animaciju zatvaranja nema, instant je ... strelica za zatvoriti
   burger menu ... a klikniti sa strane ujedno, ne bi trebalo zatvoriti."

   THE FIRST CUT WAS WRONG ABOUT THE ONE THING IT ARGUED FOR. It made the panel `position: fixed`
   and said pushing would "reflow the canvas -- every node would appear to move because the viewport
   narrowed". A canvas node is drawn at an absolute position inside a scrolling plane, so narrowing
   the viewport scrolls it, it does not move it -- and covering the palette hides the very
   components the reader came to drag. Overlaying was the worse trade and the reasoning for it was
   made up rather than measured.

   ONE MECHANISM SOLVES PUSH AND THE CLOSING ANIMATION AT ONCE, and that is why they are one change.
   The panel stays IN FLOW at its natural width and is pulled out of view with a NEGATIVE
   margin-left, which `.builder-shell`'s overflow:hidden clips. Opening sets margin-left to 0. So:

     * in flow -> it takes width -> the main pane is pushed right, no fixed positioning anywhere;
     * a transition on margin-left runs in BOTH directions, unlike the `@keyframes` it replaces,
       which could only fire on the frame the open state first rendered. That is why closing used to
       be instant: there was no closing state to animate, only a `display: none`.

   `display: none` is deliberately NOT used here for the same reason. A display toggle cannot be
   transitioned without `@starting-style` + `transition-behavior: allow-discrete`, and LESS eats
   `@starting-style` -- but this file is plain CSS, so the rule would work and is still not needed:
   margin does the job in both directions with nothing exotic.

   `visibility` rides along so a closed panel is not tab-reachable, and it is in the transition list
   so it flips only after the slide has finished rather than snapping at the start. */

.builder-nav .sidebar[b-rt16f9be07] {
    /* A flex item of the shell's row now, sized by the panel inside it rather than by itself. */
    position: static;
    width: auto;
    min-height: 0;
    height: 100%;
    padding: 0;
    background: none;
    border: 0;
    display: flex;
    flex: 0 0 auto;
}

/* No app bar and no brand: TreeBuild's own bar is the bar, and a second one would eat canvas. */
.builder-nav .sidebar-topbar[b-rt16f9be07],
.builder-nav .sidebar-brand[b-rt16f9be07] {
    display: none;
}

.builder-nav .sidebar-collapsible[b-rt16f9be07] {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    /* Narrower than the desktop rail: it stands beside the work rather than replacing the page, and
       is capped against the viewport so it cannot become most of a phone screen. */
    width: min(272px, 70vw);
    /* Pulled fully out of the layout, so a closed nav contributes ZERO width and the palette sits
       flush against the left edge. */
    margin-left: calc(-1 * min(272px, 70vw));
    visibility: hidden;
    padding: 1rem .9rem;
    overflow-y: auto;
    background: var(--sidebar-bg);
    border-right: 1px solid var(--sidebar-border);
    transition: margin-left 200ms ease-out, visibility 200ms;
}

.builder-nav .sidebar-mobile-toggle:checked ~ .sidebar-collapsible[b-rt16f9be07] {
    margin-left: 0;
    visibility: visible;
}

/* The close arrow. Hidden everywhere else -- the phone's topbar already has its own X. */
.sidebar-close[b-rt16f9be07] {
    display: none;
}

.builder-nav .sidebar-close[b-rt16f9be07] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: flex-end;
    width: 36px;
    height: 36px;
    flex: none;
    margin: 0 0 .35rem;
    border-radius: var(--radius-sm, .35rem);
    color: var(--sidebar-ink);
    font-size: 1rem;
    /* A <label> gets neither of these by default, and a control that does not react to the pointer
       does not read as a control. */
    cursor: pointer;
    transition: background-color .14s ease-out;
}

.builder-nav .sidebar-close:hover[b-rt16f9be07] {
    background: var(--sidebar-hover-bg);
}

@media (prefers-reduced-motion: reduce) {
    .builder-nav .sidebar-collapsible[b-rt16f9be07],
    .builder-nav .sidebar-close[b-rt16f9be07] {
        transition: none;
    }

    /* The group slide too. It is a small movement, but it fires on a control somebody uses to get
       anywhere in the application, so it is exactly the kind of repeated motion this query exists
       for. The groups still open and close; they just arrive. */
    [b-rt16f9be07] .sidebar-nav-section::details-content,
    [b-rt16f9be07] .sidebar-nav-group,
    [b-rt16f9be07] .sidebar-nav-group::after {
        transition: none;
    }
}
/* /Components/Layout/OnboardingTour.razor.rz.scp.css */
/* The onboarding tour (2026-09-17). Scoped rather than app.less, for the reason
   PrivacyPolicy.razor.css already gives: `npm run purge:vendor-css` is not part of `dotnet build`,
   so a selector added to app.less is compiled and then not served until somebody remembers the
   second step. A .razor.css is compiled by the build into the stylesheet App.razor already links. */

/* THE WHOLE TOUR IS ONE FIXED LAYER, and it takes the clicks. Letting them through to the page was
   the other option and it is worse here: every highlighted thing is a LINK, so a reader following
   the demonstration would navigate away mid-tour and meet step one again on the next page. */
.tour[b-5jr6vu5eu9] {
    position: fixed;
    inset: 0;
    z-index: 1200;
}

/* With no target there is nothing to cut a hole around, so the dimming has to come from the layer
   itself -- the spotlight is hidden on those steps. */
.tour[data-tour-centred][b-5jr6vu5eu9] {
    background: rgb(0 0 0 / .55);
}

/* THE SPOTLIGHT IS A TRANSPARENT BOX WITH AN ENORMOUS SHADOW. The element covers only the thing
   being pointed at; `box-shadow: 0 0 0 9999px` paints everything else. So the highlighted control
   keeps its own colours at full brightness and perfect sharpness -- no filter, no second copy of the
   element, and nothing to keep in step on a resize.

   The ring is the accent at low alpha rather than a hard border: this sits on top of the sidebar,
   which is near-black in both themes, and a 2px solid line there reads as a rendering artefact. */
.tour-spotlight[b-5jr6vu5eu9] {
    position: fixed;
    border-radius: var(--radius, .5rem);
    box-shadow: 0 0 0 9999px rgb(0 0 0 / .55);
    outline: 2px solid color-mix(in oklab, var(--accent) 70%, transparent);
    outline-offset: 2px;
    pointer-events: none;
    transition: top .22s ease, left .22s ease, width .22s ease, height .22s ease;
}

.tour-panel[b-5jr6vu5eu9] {
    position: fixed;
    width: min(23rem, calc(100vw - 2rem));
    padding: 1.15rem 1.25rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 18px 40px rgb(0 0 0 / .35);
}

/* Centred when the step has no target, or when the target is off-screen -- which on a phone is every
   sidebar step, because the whole sidebar is behind a burger there. */
.tour[data-tour-centred] .tour-panel[b-5jr6vu5eu9] {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.tour-panel:focus-visible[b-5jr6vu5eu9] {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.tour-counter[b-5jr6vu5eu9] {
    margin: 0 0 .2rem;
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--accent);
}

.tour-title[b-5jr6vu5eu9] {
    margin: 0 0 .4rem;
    font-size: 1.05rem;
    line-height: 1.3;
}

.tour-body[b-5jr6vu5eu9] {
    margin: 0 0 1rem;
    font-size: .9375rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

.tour-actions[b-5jr6vu5eu9] {
    display: flex;
    align-items: center;
    gap: .5rem;
}

/* Skip is pushed away from the other two by this, so the button that LEAVES is never next to the
   button that continues -- the two are pressed for opposite reasons and a stray click should not be
   able to end the tour. */
.tour-actions-gap[b-5jr6vu5eu9] {
    flex: 1 1 auto;
}

.tour-skip[b-5jr6vu5eu9] {
    padding-left: 0;
    padding-right: 0;
    font-size: .875rem;
    color: var(--ink-soft);
    text-decoration: underline;
}

.tour-skip:hover[b-5jr6vu5eu9] {
    color: var(--ink);
}

/* The steps are markup the module reads, never something the reader sees. `hidden` alone would be
   enough, but the tour container is `position: fixed` and a stray list would otherwise be laid out
   over the page for the frame before the module runs. */
.tour-steps[b-5jr6vu5eu9] {
    display: none;
}

.tour-token[b-5jr6vu5eu9] {
    display: none;
}

/* At phone width the panel stops trying to sit beside anything: the sidebar is not on screen to
   point at, and a 23rem panel positioned against a missing rectangle would hang off the edge. */
@media (max-width: 34rem) {
    .tour-panel[b-5jr6vu5eu9] {
        top: auto !important;
        bottom: .75rem;
        left: .75rem !important;
        right: .75rem;
        width: auto;
        transform: none;
    }

    .tour[data-tour-centred] .tour-panel[b-5jr6vu5eu9] {
        top: auto;
        left: .75rem;
        transform: none;
    }
}

/* A drifting spotlight is exactly the kind of movement this query exists for, and the tour is the
   first thing a brand-new account sees -- the worst possible moment to make somebody motion-sick. */
@media (prefers-reduced-motion: reduce) {
    .tour-spotlight[b-5jr6vu5eu9] {
        transition: none;
    }
}
/* /Components/Layout/PrintLayout.razor.rz.scp.css */
/* Bare shell for printable documents — no sidebar, no app-shell padding, plain white
   background regardless of the light/dark app chrome elsewhere (project-knowledge.md "Design
   system" tokens aren't reused here on purpose: a printed document reads as paper, not as the
   app UI). */
.print-page[b-8znl9ktzud] {
    background: #ffffff;
    min-height: 100vh;
    padding: 2rem;
}

@media print {
    .print-page[b-8znl9ktzud] {
        padding: 0;
    }
}
/* /Components/Layout/ReconnectModal.razor.rz.scp.css */
.components-reconnect-first-attempt-visible[b-lcqdyftfu2],
.components-reconnect-repeated-attempt-visible[b-lcqdyftfu2],
.components-reconnect-failed-visible[b-lcqdyftfu2],
.components-pause-visible[b-lcqdyftfu2],
.components-resume-failed-visible[b-lcqdyftfu2],
.components-rejoining-animation[b-lcqdyftfu2] {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-paused .components-pause-visible[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-retrying[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-failed[b-lcqdyftfu2],
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible[b-lcqdyftfu2] {
    display: block;
}

#components-reconnect-modal[b-lcqdyftfu2] {
    background-color: var(--panel, #ffffff);
    color: var(--ink, #1a1f27);
    width: 20rem;
    margin: 20vh auto;
    padding: 2rem;
    border: 1px solid var(--border, #e3e6ea);
    border-radius: var(--radius, 8px);
    box-shadow: var(--shadow-md, 0 4px 10px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .08));
    opacity: 0;
    transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
    animation: components-reconnect-modal-fadeOutOpacity-b-lcqdyftfu2 0.5s both;
}

#components-reconnect-modal[open][b-lcqdyftfu2] {
    animation: components-reconnect-modal-slideUp-b-lcqdyftfu2 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity-b-lcqdyftfu2 0.5s ease-in-out 0.3s;
    animation-fill-mode: both;
}

#components-reconnect-modal[b-lcqdyftfu2]::backdrop {
    background-color: rgba(0, 0, 0, 0.6);
    animation: components-reconnect-modal-fadeInOpacity-b-lcqdyftfu2 0.5s ease-in-out;
    opacity: 1;
}

@keyframes components-reconnect-modal-slideUp-b-lcqdyftfu2 {
    0% {
        transform: translateY(30px) scale(0.95);
    }

    100% {
        transform: translateY(0);
    }
}

@keyframes components-reconnect-modal-fadeInOpacity-b-lcqdyftfu2 {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

@keyframes components-reconnect-modal-fadeOutOpacity-b-lcqdyftfu2 {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

.components-reconnect-container[b-lcqdyftfu2] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

#components-reconnect-modal p[b-lcqdyftfu2] {
    margin: 0;
    text-align: center;
}

#components-reconnect-modal button[b-lcqdyftfu2] {
    border: 0;
    background-color: var(--accent, #0f7a6f);
    color: #ffffff;
    font-weight: 600;
    padding: 6px 24px;
    border-radius: var(--radius-sm, 6px);
    cursor: pointer;
}

#components-reconnect-modal button:hover[b-lcqdyftfu2] {
    background-color: var(--accent-hover, #0b5f56);
}

.components-rejoining-animation[b-lcqdyftfu2] {
    position: relative;
    width: 80px;
    height: 80px;
}

.components-rejoining-animation div[b-lcqdyftfu2] {
    position: absolute;
    border: 3px solid var(--accent, #0f7a6f);
    opacity: 1;
    border-radius: 50%;
    animation: components-rejoining-animation-b-lcqdyftfu2 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}

.components-rejoining-animation div:nth-child(2)[b-lcqdyftfu2] {
    animation-delay: -0.5s;
}

@media (max-width: 575.98px) {
    #components-reconnect-modal[b-lcqdyftfu2] {
        width: calc(100vw - 32px);
        margin: 12vh auto;
        padding: 1.5rem;
    }

    .components-rejoining-animation[b-lcqdyftfu2] {
        width: 64px;
        height: 64px;
    }
}

@keyframes components-rejoining-animation-b-lcqdyftfu2 {
    0% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    4.9% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    5% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 1;
    }

    100% {
        top: 0px;
        left: 0px;
        width: 80px;
        height: 80px;
        opacity: 0;
    }
}
/* /Components/Layout/ThemeToggle.razor.rz.scp.css */
/* Dark-mode toggle (2026-08 round) - circular icon button living on the dark --sidebar-* surface
   in BOTH its mounting locations (desktop sidebar footer AND the mobile top bar - .sidebar-topbar
   is a child of the same .sidebar panel, so it shares the same dark background), matching the
   existing .sidebar-palette-hint/.sidebar-logout-btn hover treatment in NavMenu.razor.css. The
   mobile top bar additionally merges in that file's own .sidebar-topbar-btn class (44px touch
   target) via the ButtonClass parameter - the sizing below is this component's own default,
   used as-is by the desktop footer mount. */
.theme-toggle-form[b-ym79984wfc] {
    display: inline-flex;
    margin: 0;
}

.theme-toggle-btn[b-ym79984wfc] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* WCAG 2.2 SC 2.5.8 minimum target size even on the desktop-default (smaller) sizing. */
    width: 2rem;
    height: 2rem;
    padding: 0;
    background: none;
    border: 1px solid var(--sidebar-border);
    border-radius: 999px;
    color: var(--sidebar-ink-soft);
    font-size: .85rem;
    cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

.theme-toggle-btn:hover[b-ym79984wfc] {
    color: var(--sidebar-ink);
    background: var(--sidebar-hover-bg);
    border-color: var(--sidebar-mint);
}

/* Icon shows the theme CURRENTLY in effect (moon = dark active, sun = light active), not the
   theme a click would switch to - CSS alone tracks it via [data-bs-theme] on <html>, so it stays
   correct even after theme-toggle.js flips that attribute client-side with no component re-render. */
.theme-toggle-icon-moon[b-ym79984wfc] {
    display: none;
}

[data-bs-theme="dark"] .theme-toggle-icon-sun[b-ym79984wfc] {
    display: none;
}

[data-bs-theme="dark"] .theme-toggle-icon-moon[b-ym79984wfc] {
    display: inline;
}
/* /Components/Pages/Account/Register.razor.rz.scp.css */
/* Honeypot field for HandleRegisterAsync's anti-spam check (see RegistrationGate.cs).
   Off-screen via absolute positioning rather than display:none/visibility:hidden —
   those are the first two properties naive bot scrapers check for and skip, which
   would defeat the honeypot; moving it off-screen still hides it from sighted users
   without tripping that check. Combined with tabindex="-1"/aria-hidden on the input
   itself, it's also invisible to keyboard and screen-reader users. Ported verbatim
   from G:\Solar's sibling pattern (Register.razor.css). */
.hp-field[b-8s1bvfop28] {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
/* /Components/Pages/Home.razor.rz.scp.css */
/* "Slate" stat-tile grid (Home dashboard) - plain elevated cards, replacing
   "Survey Line"'s hairline corner-tick tiles + once-only scan-line sweep. 7 tiles
   (M7a added "Leases expiring soon") don't divide evenly into a fixed 3-column
   grid without an orphan last row, so this stays flex-wrap (unchanged reasoning
   from both prior themes), just re-skinned card-by-card with real elevation
   instead of one hairline-joined block. */
.stat-tile-grid[b-dmm30q5g3n] {
    /* auto-fill grid (2026-07-28 UX pass, was flex-wrap): keeps every tile the
       same width even on a partially-filled last row - flex-grow used to
       stretch the 2 tiles of a 5+2 wrap into visually unrelated sizes. */
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 1rem;
    margin: 1.5rem 0 1.5rem;
}

.stat-tile[b-dmm30q5g3n] {
    background: var(--panel);
    border: 1px solid var(--border);
    /* Single consistent accent-colored top rule (2026-07-24 color pass) - a
       small, deliberately uniform touch of color presence on every tile
       rather than a per-tile multi-color scheme. */
    border-top: 3px solid var(--accent);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    flex: 1 1 220px;
    padding: 1.1rem 1.25rem 1.25rem;
}

.stat-tile-row[b-dmm30q5g3n] {
    margin: 0;
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .85rem;
    font-weight: 500;
    color: var(--ink-soft);
}

.stat-tile-number[b-dmm30q5g3n] {
    /* IBM Plex Mono, not the body/heading face - known gotcha, do not
       rediscover it: a bold display font's bare "0" can render as a solid
       blob indistinguishable from a dot at large sizes, which is
       launch-blocking on a brand-new tenant's all-zero Home dashboard. Plex
       Mono's zero carries a dot/slash that disambiguates 0/O. */
    display: block;
    margin-top: .35rem;
    font-family: 'IBM Plex Mono', monospace;
    font-weight: 600;
    font-size: 2rem;
    color: var(--accent);
    text-decoration: none;
    font-variant-numeric: tabular-nums;
}

.stat-tile-number:hover[b-dmm30q5g3n] {
    color: var(--accent-hover);
    text-decoration: underline;
}

/* Currency unit on money tiles - quiet, small, never competing with the number. */
.stat-tile-unit[b-dmm30q5g3n] {
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-size: .78rem;
    font-weight: 600;
    color: var(--ink-faint);
    margin-left: .4rem;
    letter-spacing: .04em;
}

/* Alert-state tiles - the ONLY per-tile color deviation from the uniform accent
   top rule: a tile whose count demands action shifts its rule + number to the
   status color, so "Overdue invoices 2" no longer reads identically to a
   harmless zero. Zero-count tiles keep the calm uniform accent. */
.stat-tile-warn[b-dmm30q5g3n] {
    border-top-color: var(--warn);
}

.stat-tile-warn .stat-tile-number[b-dmm30q5g3n] {
    color: var(--warn);
}

.stat-tile-danger[b-dmm30q5g3n] {
    border-top-color: var(--danger);
}

.stat-tile-danger .stat-tile-number[b-dmm30q5g3n] {
    color: var(--danger);
}

/* ==== Page head (tree name + app kicker) ==== */

.home-head[b-dmm30q5g3n] {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.25rem;
}

.home-kicker[b-dmm30q5g3n] {
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--accent);
    margin: 0 0 .2rem;
}

.home-title[b-dmm30q5g3n] {
    margin-bottom: 0;
}

.home-head-action[b-dmm30q5g3n] {
    flex: none;
    margin-top: .35rem;
}

/* ==== "Needs attention" panel ==== */

.attn-panel[b-dmm30q5g3n] {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1.1rem 1.25rem;
    margin-bottom: 1.5rem;
}

.attn-live[b-dmm30q5g3n] {
    border-left: 3px solid var(--warn);
}

.attn-quiet[b-dmm30q5g3n] {
    border-left: 3px solid var(--ok);
}

.attn-heading[b-dmm30q5g3n] {
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0 0 .6rem;
}

.attn-list[b-dmm30q5g3n] {
    list-style: none;
    margin: 0;
    padding: 0;
}

.attn-item[b-dmm30q5g3n] {
    display: flex;
    align-items: center;
    gap: .65rem;
    padding: .5rem .35rem;
    border-radius: var(--radius-sm);
    text-decoration: none;
    color: var(--ink);
}

.attn-item:hover[b-dmm30q5g3n] {
    background: var(--bg-subtle);
}

.attn-list li + li .attn-item[b-dmm30q5g3n] {
    border-top: 1px solid var(--border);
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

.attn-dot[b-dmm30q5g3n] {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

.attn-dot-danger[b-dmm30q5g3n] { background: var(--danger); }
.attn-dot-warn[b-dmm30q5g3n] { background: var(--warn); }

.attn-label[b-dmm30q5g3n] {
    font-weight: 500;
}

.attn-value[b-dmm30q5g3n] {
    margin-left: auto;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .9rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

.attn-chevron[b-dmm30q5g3n] {
    color: var(--ink-faint);
    font-size: 1.1rem;
    line-height: 1;
}

.attn-allclear[b-dmm30q5g3n] {
    margin: 0;
    color: var(--ink-soft);
    font-size: .92rem;
}

.attn-check[b-dmm30q5g3n] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--ok-soft);
    color: var(--ok);
    font-size: .75rem;
    font-weight: 700;
    margin-right: .35rem;
}

/* ==== Lower band: recent activity + quick actions ==== */

.home-lower[b-dmm30q5g3n] {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(220px, 1fr);
    gap: 1rem;
    align-items: start;
}

@media (max-width: 900px) {
    .home-lower[b-dmm30q5g3n] {
        grid-template-columns: 1fr;
    }
}

.home-section-heading[b-dmm30q5g3n] {
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0 0 .6rem;
}

.home-activity[b-dmm30q5g3n],
.home-quick[b-dmm30q5g3n] {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1.1rem 1.25rem 1.25rem;
}

.home-activity-empty[b-dmm30q5g3n] {
    margin: 0;
    color: var(--ink-faint);
    font-size: .92rem;
}

.activity-list[b-dmm30q5g3n] {
    list-style: none;
    margin: 0;
    padding: 0;
}

.activity-row[b-dmm30q5g3n] {
    display: flex;
    align-items: baseline;
    gap: .85rem;
    padding: .45rem 0;
}

.activity-row + .activity-row[b-dmm30q5g3n] {
    border-top: 1px solid var(--border);
}

.activity-when[b-dmm30q5g3n] {
    flex: none;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink-faint);
}

.activity-text[b-dmm30q5g3n] {
    font-size: .92rem;
    color: var(--ink);
    min-width: 0;
}

.activity-sep[b-dmm30q5g3n] {
    color: var(--ink-faint);
    margin: 0 .15rem;
}

.activity-action[b-dmm30q5g3n] {
    color: var(--ink-soft);
    margin-left: .35rem;
}

.quick-actions[b-dmm30q5g3n] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.quick-actions .btn[b-dmm30q5g3n] {
    text-align: left;
}

/* One restrained fade+rise on load, gated by prefers-reduced-motion - the single
   deliberate moment of motion this theme allows itself. No scan-line, no sweep,
   nothing else animates. */
@media (prefers-reduced-motion: no-preference) {
    .stat-tile[b-dmm30q5g3n] {
        opacity: 0;
        animation: slateRise-b-dmm30q5g3n 150ms ease-out forwards;
    }

    .stat-tile-grid .stat-tile:nth-child(1)[b-dmm30q5g3n] { animation-delay: 0ms; }
    .stat-tile-grid .stat-tile:nth-child(2)[b-dmm30q5g3n] { animation-delay: 40ms; }
    .stat-tile-grid .stat-tile:nth-child(3)[b-dmm30q5g3n] { animation-delay: 80ms; }
    .stat-tile-grid .stat-tile:nth-child(4)[b-dmm30q5g3n] { animation-delay: 120ms; }
    .stat-tile-grid .stat-tile:nth-child(5)[b-dmm30q5g3n] { animation-delay: 160ms; }
    .stat-tile-grid .stat-tile:nth-child(6)[b-dmm30q5g3n] { animation-delay: 200ms; }
    .stat-tile-grid .stat-tile:nth-child(7)[b-dmm30q5g3n] { animation-delay: 240ms; }
}

@keyframes slateRise-b-dmm30q5g3n {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

.home-hero[b-dmm30q5g3n] {
    text-align: center;
    padding: 4rem 1rem;
}

.home-hero h1[b-dmm30q5g3n] {
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-weight: 700;
    font-size: 2.5rem;
    overflow-wrap: break-word;
}

/* Carried forward from both prior themes: the brand headline never had a
   narrow-viewport check before M7's landing-page build - keep the fix. */
@media (max-width: 560px) {
    .home-hero h1[b-dmm30q5g3n] {
        font-size: 1.8rem;
    }
}

.home-hero-eyebrow[b-dmm30q5g3n] {
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-weight: 600;
    font-size: .85rem;
    color: var(--accent);
    margin: 0 0 .85rem;
}

.home-hero-tagline[b-dmm30q5g3n] {
    max-width: 640px;
    margin: 1.25rem auto 0;
    color: var(--ink-soft);
    font-size: 1.05rem;
    line-height: 1.6;
}

.home-hero-actions[b-dmm30q5g3n] {
    margin-top: 1.5rem;
    display: flex;
    gap: .75rem;
    justify-content: center;
}

/* ==== Landing sections (anonymous visitors only) ==== */

.landing-section[b-dmm30q5g3n] {
    margin: 5rem 0;
}

.landing-eyebrow[b-dmm30q5g3n] {
    font-size: 1.4rem;
    text-align: center;
    margin: 0 0 2rem;
}

/* Feature grid - 2x2 desktop / 1 col mobile. */
.feature-grid[b-dmm30q5g3n] {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1rem;
}

@media (max-width: 700px) {
    .feature-grid[b-dmm30q5g3n] {
        grid-template-columns: 1fr;
    }
}

.feature-card[b-dmm30q5g3n] {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1.5rem 1.5rem 1.75rem;
}

.feature-card h3[b-dmm30q5g3n] {
    font-size: 1.05rem;
    margin: 0 0 .5rem;
}

.feature-card p[b-dmm30q5g3n] {
    margin: 0;
    color: var(--ink-soft);
    font-size: .92rem;
    line-height: 1.55;
}

/* Plain Font Awesome glyph on an accent-tinted rounded square - ordinary SaaS
   feature-icon treatment, replacing the retired themes' bespoke CSS-drawn marks
   (a crosshair/survey-benchmark glyph among them - deliberately not reused here). */
.feature-icon[b-dmm30q5g3n] {
    width: 40px;
    height: 40px;
    flex: none;
    border-radius: 10px;
    background: var(--accent-soft);
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    margin-bottom: 1rem;
}

@media (prefers-reduced-motion: no-preference) {
    .feature-card[b-dmm30q5g3n] {
        opacity: 0;
        animation: slateRise-b-dmm30q5g3n 150ms ease-out forwards;
    }

    .feature-grid .feature-card:nth-child(1)[b-dmm30q5g3n] { animation-delay: 0ms; }
    .feature-grid .feature-card:nth-child(2)[b-dmm30q5g3n] { animation-delay: 40ms; }
    .feature-grid .feature-card:nth-child(3)[b-dmm30q5g3n] { animation-delay: 80ms; }
    .feature-grid .feature-card:nth-child(4)[b-dmm30q5g3n] { animation-delay: 120ms; }
}

/* How it works - 3 steps, horizontal desktop / stacked mobile. */
.steps-row[b-dmm30q5g3n] {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
}

@media (max-width: 700px) {
    .steps-row[b-dmm30q5g3n] {
        grid-template-columns: 1fr;
    }
}

.step[b-dmm30q5g3n] {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1.5rem 1.5rem 1.75rem;
}

.step-number[b-dmm30q5g3n] {
    display: block;
    font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    font-weight: 700;
    font-size: 1.4rem;
    color: var(--accent);
    margin-bottom: .5rem;
}

.step h3[b-dmm30q5g3n] {
    font-size: 1.02rem;
    margin: 0 0 .5rem;
}

.step p[b-dmm30q5g3n] {
    margin: 0;
    color: var(--ink-soft);
    font-size: .92rem;
    line-height: 1.55;
}

/* Closing CTA band - subtle panel background, centered. Accent-soft (was
   --bg-subtle gray) - 2026-07-24: ties this section into the same
   accent-tinted color story as the sidebar/table chrome instead of a flat
   gray, and reinforces the sign-up CTA with the brand color. */
.landing-cta[b-dmm30q5g3n] {
    background: var(--accent-soft);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    padding: 3.5rem 1.5rem;
    text-align: center;
}

.landing-cta-heading[b-dmm30q5g3n] {
    font-size: 1.7rem;
    margin: 0 0 1.5rem;
}
/* /Components/Pages/Legal/PrivacyPolicy.razor.rz.scp.css */
/* Scoped, not app.less, on purpose: `npm run purge:vendor-css` is not part of `dotnet build`, so a
   new custom property or selector in app.less is compiled and then not served until somebody
   remembers to purge (CLAUDE.md records that trap, and it has cost a black chart and a missing icon
   already). A .razor.css file is compiled by the build into
   Proceptio.MontagemRoots.Blazor.styles.css, which App.razor already links -- no second step. */

.privacy[b-amqjx9l8rh] {
    /* Narrower than the app's 1360px shell. A policy is read, not scanned, and a 40rem measure is
       about 75 characters -- the line length people actually finish. */
    max-width: 44rem;
}

.privacy-updated[b-amqjx9l8rh] {
    color: var(--ink-soft);
    font-size: .85rem;
    margin-bottom: 1.25rem;
}

/* The way out, given the weight of a call to action rather than a link buried in a paragraph. */
.privacy-act[b-amqjx9l8rh] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1rem;
    margin: 1.5rem 0 2rem;
    padding: 1rem 1.15rem;
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: .5rem;
    background: var(--bg-subtle);
}

.privacy-act-text[b-amqjx9l8rh] {
    flex: 1 1 16rem;
    margin: 0;
}

.privacy-act-mail[b-amqjx9l8rh] {
    font-size: .9rem;
}

.privacy-section[b-amqjx9l8rh] {
    margin-bottom: 1.75rem;
}

.privacy-section h2[b-amqjx9l8rh] {
    margin-bottom: .6rem;
}

.privacy-section p[b-amqjx9l8rh] {
    margin-bottom: .7rem;
}

.privacy-section p:last-child[b-amqjx9l8rh] {
    margin-bottom: 0;
}
/* /Components/Pages/Legal/TermsOfUse.razor.rz.scp.css */
/* Scoped, not app.less, for the reason PrivacyPolicy.razor.css already gives: the purge
   (`npm run purge:vendor-css`) is not part of `dotnet build`, so a selector added to app.less is
   compiled and then not served until somebody remembers the second step.

   These rules are near-twins of PrivacyPolicy.razor.css and that duplication is deliberate. Blazor
   CSS isolation stops a component's stylesheet at that component's own markup -- a scoped `.privacy`
   rule carries PrivacyPolicy's scope attribute and cannot match anything here -- so the choice was
   between four small repeated declarations and moving both pages' layout into app.less, where every
   future edit needs the purge to reach a browser. Four declarations is the cheaper mistake. */

.terms[b-dqddj16vwj] {
    /* Same measure as /privacy: about 75 characters, the line length people actually finish. */
    max-width: 44rem;
}

.terms-updated[b-dqddj16vwj] {
    color: var(--ink-soft);
    font-size: .85rem;
    margin-bottom: 1.25rem;
}

/* The privacy statement, given the weight of a call to action rather than a link in a paragraph.
   Somebody accepting these terms is being told to read that document, and a link buried mid-sentence
   is a link nobody follows. */
.terms-act[b-dqddj16vwj] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1rem;
    margin: 1.5rem 0 2rem;
    padding: 1rem 1.15rem;
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: .5rem;
    background: var(--bg-subtle);
}

.terms-act-text[b-dqddj16vwj] {
    flex: 1 1 16rem;
    margin: 0;
}

.terms-act-mail[b-dqddj16vwj] {
    font-size: .9rem;
}

.terms-section[b-dqddj16vwj] {
    margin-bottom: 1.75rem;
}

.terms-section h2[b-dqddj16vwj] {
    margin-bottom: .6rem;
}

.terms-section p[b-dqddj16vwj] {
    margin-bottom: .7rem;
}

.terms-section p:last-child[b-dqddj16vwj] {
    margin-bottom: 0;
}
/* /Components/Pages/People/PersonChart.razor.rz.scp.css */
/* PersonChart-only styles.

   The node-label rules that used to live here (.chart-node and friends) moved to app.less on
   2026-08-22. They were written for this component and then reused by /tree/chart, which reused the
   class NAMES and got none of the styles - Blazor scoped CSS rewrites its selectors with a
   per-component attribute, so it silently does not apply anywhere else. The visible result was the
   thing the clipping rule existed to prevent: long Croatian surnames overflowing their boxes and
   colliding with the next generation. One definition, in app.less, for both charts.

   What stays here is what only this component has: the pan/zoom affordances driven by
   PersonChart.razor.js. */

/* Pan/zoom affordances. Applied only once PersonChart.razor.js has initialised (.is-interactive), so
   without JS the chart shows no gesture cues it cannot honour -- it just scrolls in its box. */
svg.is-interactive[b-nc5gqvtiov] { cursor: grab; touch-action: none; }
svg.is-interactive.is-panning[b-nc5gqvtiov] { cursor: grabbing; }
/* /Components/Pages/People/PersonDetail.razor.rz.scp.css */
/* WCAG 2.5.8 Target Size (Minimum), AA -- 24x24 CSS px.
   ---------------------------------------------------------------------------------------------
   MEASURED, NOT GUESSED (2026-09-18). A sweep of every pointer target on every route, applying the
   standard's own exceptions, found exactly two failures in the whole application and both were here:
   the family links in the relatives spine came out 180x19 and 165x19. Nineteen pixels tall.

   THE SPACING EXCEPTION DOES NOT RESCUE THEM, which is the reason this is a real finding rather than
   a number. An undersized target passes if a 24px circle centred on it touches nothing else -- and
   these are stacked list items a few pixels apart, so each one's circle lands on its neighbour.

   The fix is vertical padding rather than a height, so it still works when a family label wraps onto
   two lines -- which it does at narrow widths, because the label is two names and a separator.
   `inline-block` is what makes the padding count toward the hit box at all: an inline element's
   vertical padding paints but does not enlarge the box the pointer is tested against.

   This is a genuine usability win rather than conformance paperwork. A family tree's readers skew
   older, and a 19px line of text is precisely what that audience misses. */
.person-family-list[b-xzxgpzvehs] {
    margin-bottom: 0;
}

.person-family-list li + li[b-xzxgpzvehs] {
    margin-top: .15rem;
}

.person-family-list a[b-xzxgpzvehs] {
    display: inline-block;
    padding-block: .3rem;
    min-height: 24px;
}
/* /Components/Pages/Tree/TreeBuild.razor.rz.scp.css */
/*
    The live builder's own styles.

    A SCOPED .razor.css, not app.less, and that is the documented rule rather than a preference: the
    LESS purge is not part of `dotnet build`, so a rule added to app.less is compiled and then not
    served until somebody remembers `npm run purge:vendor-css` -- a trap that has already cost this
    repository a black chart and a missing icon. A scoped file is compiled straight into
    Proceptio.MontagemRoots.Blazor.styles.css, which App.razor already links, and cannot be forgotten.

    It is also plain CSS rather than LESS, which is why the @keyframes below actually work: LESS reads
    `@name { ... }` as a detached-ruleset assignment and silently drops the block, with only a
    "warn app: Invalid property name" in the build output. That trap is documented for @starting-style
    and applies to any at-rule written in app.less.

    Colours come from the existing tokens (--sex-*, --ink, --accent, --kin), so the canvas cannot drift
    from the charts and dark mode needs nothing here: those tokens already flip.

    MOTION IS ON A BUDGET (founder: "animacije da budu vise smooth, vise fluidne, intuitivne").
    Everything moves in 140-220ms on an ease-out curve, which is long enough to be followed and short
    enough not to be waited for; nothing loops, nothing bounces. And every one of them is wrapped by
    prefers-reduced-motion at the bottom, because a canvas that animates on every drag is exactly the
    kind of page that triggers vestibular symptoms.
*/

/* ------------------------------------------------------------------ the bar */

.build-bar[b-0e0y5dggtn] {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex: 0 0 auto;
    padding: .5rem .9rem;
    border-bottom: 1px solid var(--line, rgba(128, 128, 128, .3));
}

/* The burger. Sized like the sidebar's own topbar button (44px) so the touch target is the one the
   rest of the app uses, and it is the first thing in the bar because that is where a reader looks for
   navigation. `cursor: pointer` is not cosmetic here: a <label> does not get it by default, and a
   control that does not react to the pointer does not read as a control. */
.build-burger[b-0e0y5dggtn] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex: none;
    margin: 0;
    border-radius: var(--radius-sm, .35rem);
    color: var(--ink, #e8e8ea);
    font-size: 1.05rem;
    cursor: pointer;
    transition: background-color .14s ease-out;
}

.build-burger:hover[b-0e0y5dggtn] {
    background: var(--bg-subtle, rgba(128, 128, 128, .18));
}

.build-bar-actions[b-0e0y5dggtn] {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: .4rem;
}

/* The status line fades in and stays; it never moves the layout, because a bar that reflows when a
   save lands is a bar that makes the reader lose the button they were about to press. */
.build-status[b-0e0y5dggtn] {
    font-size: .78rem;
    color: var(--ink-soft, #8b8b93);
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity .18s ease-out, transform .18s ease-out;
}

.build-status.is-shown[b-0e0y5dggtn] {
    opacity: 1;
    transform: none;
}

.build-zoom[b-0e0y5dggtn] {
    display: inline-flex;
    gap: .15rem;
}

.build-zoom-level[b-0e0y5dggtn] {
    min-width: 3.6rem;
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ the three columns */

.build-body[b-0e0y5dggtn] {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    /* The whole point of the full-bleed layout: palette hard left, details hard right, canvas taking
       everything that is left over. */
    width: 100%;
}

.build-palette[b-0e0y5dggtn] {
    flex: 0 0 12.5rem;
    width: 12.5rem;
    padding: .8rem;
    border-right: 1px solid var(--line, rgba(128, 128, 128, .3));
    overflow-y: auto;
}

.build-details[b-0e0y5dggtn] {
    flex: 0 0 18rem;
    width: 18rem;
    padding: .8rem;
    border-left: 1px solid var(--line, rgba(128, 128, 128, .3));
    overflow-y: auto;
    /* Slides in from its own edge rather than appearing: the reader double-clicked a box on the left,
       and motion from the right is what connects the two halves of that gesture. */
    animation: build-panel-in-b-0e0y5dggtn .2s ease-out;
}

@keyframes build-panel-in-b-0e0y5dggtn {
    from { opacity: 0; transform: translateX(12px); }
    to   { opacity: 1; transform: none; }
}

.build-palette-heading[b-0e0y5dggtn] {
    font-size: .68rem;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-soft, #8b8b93);
    margin-bottom: .45rem;
}

.build-hint[b-0e0y5dggtn] {
    font-size: .73rem;
    line-height: 1.35;
    color: var(--ink-soft, #8b8b93);
    margin-bottom: .6rem;
}

.build-hint-armed[b-0e0y5dggtn] {
    color: var(--accent, #b8860b);
}

/* ------------------------------------------------------------------ the palette chips */

.build-chip[b-0e0y5dggtn] {
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
    margin-bottom: .3rem;
    padding: .42rem .55rem;
    text-align: left;
    font-size: .8rem;
    background: transparent;
    border: 1px solid var(--line, rgba(128, 128, 128, .35));
    border-radius: .4rem;
    color: inherit;
    cursor: grab;
    transition: border-color .15s ease-out, box-shadow .15s ease-out, transform .12s ease-out;
}

.build-chip:hover[b-0e0y5dggtn] {
    border-color: var(--accent, #b8860b);
    transform: translateX(2px);
}

.build-chip:active[b-0e0y5dggtn] {
    transform: scale(.98);
}

/* Armed reads as armed at a glance, and the ring is drawn with box-shadow rather than a border so the
   chip does not change size and shuffle the ones below it. */
.build-chip.is-armed[b-0e0y5dggtn] {
    border-color: var(--accent, #b8860b);
    box-shadow: inset 0 0 0 1px var(--accent, #b8860b);
}

.build-chip-dot[b-0e0y5dggtn] {
    width: .8rem;
    height: .8rem;
    border-radius: .2rem;
    flex: 0 0 auto;
}

.build-chip-m .build-chip-dot[b-0e0y5dggtn] { background: var(--sex-male-bg, #2b4b6f); }
.build-chip-f .build-chip-dot[b-0e0y5dggtn] { background: var(--sex-female-bg, #6f2b52); }
.build-chip-u .build-chip-dot[b-0e0y5dggtn] { background: var(--sex-unknown-bg, #6f5c2b); }

/* A relationship chip draws its own line, so the palette IS the diagram's legend. */
.build-chip-line[b-0e0y5dggtn] {
    width: 2.1rem;
    height: .75rem;
    flex: 0 0 auto;
    overflow: visible;
}

.build-chip-link[b-0e0y5dggtn] {
    cursor: crosshair;
}

/* The Move tool. Its swatch is a glyph rather than a colour block, because it is not a person and
   not a relationship -- it is the absence of both, and a coloured square would suggest it drops
   something on the canvas. */
.build-chip-move[b-0e0y5dggtn] {
    gap: .5rem;
}

.build-chip-move i[b-0e0y5dggtn] {
    width: 1.1rem;
    text-align: center;
    color: var(--ink-soft, #8b8b93);
}

.build-chip-move.is-armed i[b-0e0y5dggtn] {
    color: var(--accent, #b8860b);
}

/* THE RUBBER-BAND LINE while a relationship is being dragged. Brighter than a settled wire and
   dashed short, so it reads as "not yet" rather than as one more edge in the diagram -- the reader
   should be able to tell at a glance which lines are recorded and which one is still in their hand.
   `pointer-events: none` matters: without it the line under the cursor becomes the element
   elementFromPoint returns, and every link would land on the line instead of on a box. */
.build-wire-pending[b-0e0y5dggtn] {
    stroke: var(--accent, #b8860b);
    stroke-width: 2;
    stroke-dasharray: 5 4;
    pointer-events: none;
    opacity: .95;
}

/* The box the pointer is over during a link drag. The same brass as the pending line, so the pair
   reads as one gesture: this line, into that person. */
.build-node.is-link-target[b-0e0y5dggtn] {
    border-color: var(--accent, #b8860b);
    box-shadow: 0 0 0 2px var(--accent, #b8860b);
}

/* ------------------------------------------------------------------ the canvas */

.build-viewport[b-0e0y5dggtn] {
    flex: 1 1 auto;
    min-width: 0;
    overflow: auto;
    position: relative;
    background-image:
        linear-gradient(to right, rgba(128, 128, 128, .10) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(128, 128, 128, .10) 1px, transparent 1px);
    background-size: 20px 20px;
}

/* The scaled surface. transform-origin at the top left keeps canvas coordinates and stored
   coordinates in the same relationship at every zoom -- with a centred origin, every node would
   appear to move when only the scale changed. The fixed size gives the viewport something definite
   to scroll, which a transform alone does not. */
.build-canvas[b-0e0y5dggtn] {
    position: relative;
    width: 4000px;
    height: 3000px;
    transform-origin: 0 0;
    transition: transform .16s ease-out;
}

.build-empty[b-0e0y5dggtn] {
    position: absolute;
    top: 22%;
    left: 2rem;
    max-width: 28rem;
    color: var(--ink-soft, #8b8b93);
    font-size: .9rem;
    line-height: 1.5;
}

/* The wires fill the surface and never take pointer events -- a line must not be able to swallow a
   click meant for the canvas underneath it. */
.build-wires[b-0e0y5dggtn] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    overflow: visible;
}

.build-wire[b-0e0y5dggtn] {
    stroke-width: 2;
    fill: none;
}

/* PARTNER: a solid bar. CHILD/PARENT: dashed, with a dot at the parent end. Three tools in the
   palette, two styles on the diagram -- "A is the parent of B" and "B is the child of A" are the same
   edge, and drawing them differently would claim a distinction the schema does not hold. */
.build-wire-partner[b-0e0y5dggtn] {
    stroke: var(--accent, #b8860b);
}

.build-wire-parent[b-0e0y5dggtn] {
    stroke: var(--ink-soft, #8b8b93);
    stroke-dasharray: 5 4;
}

.build-wire-endcap[b-0e0y5dggtn] {
    fill: var(--ink-soft, #8b8b93);
    stroke: none;
}

/* ------------------------------------------------------------------ a person */

.build-node[b-0e0y5dggtn] {
    position: absolute;
    width: 140px;
    min-height: 52px;
    padding: .4rem .5rem;
    border-radius: .45rem;
    border: 1px solid var(--line, rgba(128, 128, 128, .4));
    background: var(--sex-unknown-bg, #6f5c2b);
    color: var(--ink, #eee);
    cursor: grab;
    user-select: none;
    /* The gesture is Pointer Events now (wwwroot/js/build-drag.js), and without this the browser
       claims a touch drag for scrolling before the handler ever sees a pointermove. */
    touch-action: none;
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* Moving is animated so a drop READS as the box travelling to where it was put, rather than
       teleporting. left/top rather than translate because the stored position IS left/top, and one
       source of truth for where a node is beats two that have to agree. */
    transition: left .18s ease-out, top .18s ease-out, box-shadow .15s ease-out, transform .12s ease-out;
    animation: build-node-in-b-0e0y5dggtn .22s ease-out;
}

/* WHILE THE POINTER HOLDS IT: no transition, or every frame eases toward the last position instead
   of landing on it -- which is exactly the "smooth only near the end" the founder described, because
   the box was being animated toward a position reported once at the drop. It also lifts, so the box
   being moved is the one on top, and takes the closed-hand cursor a native HTML5 drag never allowed
   (the browser owned the pointer then and drew copy/no-drop instead). */
.build-node.is-dragging[b-0e0y5dggtn] {
    transition: none;
    cursor: grabbing;
    z-index: 4;
    box-shadow: 0 .6rem 1.4rem rgba(0, 0, 0, .45);
}

/* A press that has not travelled yet still reads as held. */
.build-node:active[b-0e0y5dggtn] {
    cursor: grabbing;
}

@keyframes build-node-in-b-0e0y5dggtn {
    from { opacity: 0; transform: scale(.9); }
    to   { opacity: 1; transform: scale(1); }
}

.build-node:hover[b-0e0y5dggtn] {
    box-shadow: 0 .2rem .7rem rgba(0, 0, 0, .3);
}

.build-node-m[b-0e0y5dggtn] { background: var(--sex-male-bg, #2b4b6f); }
.build-node-f[b-0e0y5dggtn] { background: var(--sex-female-bg, #6f2b52); }
.build-node-u[b-0e0y5dggtn] { background: var(--sex-unknown-bg, #6f5c2b); }

.build-node.is-selected[b-0e0y5dggtn] {
    box-shadow: 0 0 0 2px var(--accent, #b8860b);
}

/* A BOX IS FOCUSABLE NOW (tabindex="0", role="button"), so it needs a focus ring a keyboard reader
   can actually see -- and it must NOT be the same mark as .is-selected, or "where am I" and "what is
   armed" become one signal. Selection is the brass ring above; focus is a lighter outline outside
   it, so a box that is both focused and selected shows both rather than one winning.

   `outline` rather than a second box-shadow on purpose: they stack without either having to know
   about the other, and outline-offset puts this one clear of the selection ring. */
.build-node:focus-visible[b-0e0y5dggtn] {
    outline: 2px solid var(--ink, #e9ebef);
    outline-offset: 3px;
}

/* The first half of a link, and it PULSES -- the one place a loop is right, because the reader is
   mid-gesture and the box is waiting for them to pick the other end. */
.build-node.is-linking[b-0e0y5dggtn] {
    box-shadow: 0 0 0 2px var(--kin, #6b5bd6);
    animation: build-node-pulse-b-0e0y5dggtn 1.1s ease-in-out infinite;
}

@keyframes build-node-pulse-b-0e0y5dggtn {
    0%, 100% { box-shadow: 0 0 0 2px var(--kin, #6b5bd6); }
    50%      { box-shadow: 0 0 0 5px rgba(107, 91, 214, .25); }
}

/* While a relationship tool is armed every box is a candidate, so the cursor says so on all of them
   rather than only on a handle somebody has to find. */
.build-node.is-targetable[b-0e0y5dggtn] {
    cursor: crosshair;
}

.build-node.is-targetable:hover[b-0e0y5dggtn] {
    box-shadow: 0 0 0 2px var(--kin, #6b5bd6);
}

.build-node-name[b-0e0y5dggtn] {
    font-size: .82rem;
    font-weight: 600;
    line-height: 1.2;
    overflow-wrap: anywhere;
}

.build-node-years[b-0e0y5dggtn] {
    font-size: .7rem;
    opacity: .8;
}

/* ------------------------------------------------------------------ the inline prompt */

/* ANCHORED TO THE CLICK POINT (founder, 2026-09-08: "treba ostati fixan na mjestu gdje pustim lijevi
   click na canvasu"). Absolute inside `.build-canvas`, so it pans with the plane and stays over its
   own point; `left`/`top` come from `_placing`, which is the coordinate the person will be created
   at, so the panel and the outcome cannot disagree.

   `transform-origin: 0 0` is what makes the inline `scale(1 / zoom)` in the markup anchor rather than
   drift: the top-left corner IS the point being pointed at, and a centred origin would slide the
   panel off it at every zoom step. */
.build-prompt[b-0e0y5dggtn] {
    position: absolute;
    transform-origin: 0 0;
    z-index: 5;
    width: 15rem;
    padding: .6rem;
    border-radius: .45rem;
    border: 1px solid var(--accent, #b8860b);
    background: var(--panel, #ffffff);
    box-shadow: 0 .5rem 1.2rem rgba(0, 0, 0, .4);
    animation: build-prompt-in-b-0e0y5dggtn .16s ease-out;
}

@keyframes build-prompt-in-b-0e0y5dggtn {
    from { opacity: 0; transform: scale(.96) translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------------ narrow screens */

/* Below 1000px three columns cannot coexist. The palette becomes a strip across the top and the
   details panel goes under the canvas; the canvas keeps its own scroll, so nothing is lost.

   Touch is a separate matter and is NOT solved here: HTML5 drag events do not fire on touch at all,
   so on a phone this page is arm-and-tap rather than drag -- which is the gesture the founder asked
   for on mobile anyway -- and the forms remain the way to build. */
@media (max-width: 1000px) {
    .build-body[b-0e0y5dggtn] {
        flex-direction: column;
    }

    .build-palette[b-0e0y5dggtn],
    .build-details[b-0e0y5dggtn] {
        flex: 0 0 auto;
        width: auto;
        border-left: 0;
        border-right: 0;
        border-bottom: 1px solid var(--line, rgba(128, 128, 128, .3));
    }

    .build-palette[b-0e0y5dggtn] {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: .3rem;
    }

    .build-palette .build-chip[b-0e0y5dggtn] {
        width: auto;
        margin-bottom: 0;
    }

    .build-palette .build-hint[b-0e0y5dggtn] {
        flex: 1 1 100%;
        margin-bottom: .2rem;
    }

    .build-viewport[b-0e0y5dggtn] {
        min-height: 55vh;
    }

    .build-details[b-0e0y5dggtn] {
        animation: none;
    }
}

/* ------------------------------------------------------------------ reduced motion */

/* One block, at the end, turning off every animation and transition above. A drag-heavy canvas is
   precisely the page where motion sensitivity matters, and honouring the preference is not optional
   just because the motion is tasteful. */
@media (prefers-reduced-motion: reduce) {
    .build-status[b-0e0y5dggtn],
    .build-canvas[b-0e0y5dggtn],
    .build-chip[b-0e0y5dggtn],
    .build-node[b-0e0y5dggtn],
    .build-burger[b-0e0y5dggtn] {
        transition: none;
    }

    .build-details[b-0e0y5dggtn],
    .build-node[b-0e0y5dggtn],
    .build-prompt[b-0e0y5dggtn] {
        animation: none;
    }

    .build-node.is-linking[b-0e0y5dggtn] {
        animation: none;
        box-shadow: 0 0 0 3px var(--kin, #6b5bd6);
    }

    .build-chip:hover[b-0e0y5dggtn],
    .build-chip:active[b-0e0y5dggtn] {
        transform: none;
    }
}


/* ==== Deleting a person: the confirmation (2026-09-08) ====
   Blazor-rendered rather than a native <dialog>, because this one is driven by component state and
   `showModal()` would have to be called through JS interop on every open -- state and DOM saying the
   same thing twice, with a render in between to get them out of step. A scrim plus a centred box is
   the whole of what showModal() was giving us here; the focus trap it also gives is the one real
   loss, and this dialog has two buttons and no fields. */
.build-modal-scrim[b-0e0y5dggtn] {
    position: fixed;
    inset: 0;
    z-index: 1050;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, .55);
}

.build-modal[b-0e0y5dggtn] {
    width: min(24rem, 92vw);
    padding: 1.1rem;
    border: 1px solid var(--line, rgba(128, 128, 128, .35));
    border-radius: .6rem;
    background: var(--panel, #ffffff);
    box-shadow: 0 1.2rem 3rem rgba(0, 0, 0, .5);
}

.build-modal-title[b-0e0y5dggtn] {
    margin: 0 0 .45rem;
    font-size: 1rem;
}

.build-modal-message[b-0e0y5dggtn] {
    margin: 0 0 1rem;
    color: var(--ink-soft, #b9b9c0);
}

.build-modal-actions[b-0e0y5dggtn] {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
}

/* The panel's destructive corner: at the bottom, set apart, so it is not one mis-click from a field
   that saves on change. */
.build-details-danger[b-0e0y5dggtn] {
    margin-top: 1.1rem;
    padding-top: .8rem;
    border-top: 1px solid var(--line, rgba(128, 128, 128, .3));
}

.build-details-danger-note[b-0e0y5dggtn] {
    margin: .4rem 0 0;
    font-size: .72rem;
    color: var(--ink-soft, #8b8b93);
}

@media (prefers-reduced-motion: no-preference) {
    .build-modal[b-0e0y5dggtn] {
        animation: build-modal-in-b-0e0y5dggtn .14s ease-out;
    }
}

@keyframes build-modal-in-b-0e0y5dggtn {
    from { opacity: 0; transform: translateY(-.4rem) scale(.98); }
    to   { opacity: 1; transform: none; }
}


/* ==== Shortcut tooltips, the delete tool, and the wire hit targets (2026-09-08) ====

   The tooltip is markup and CSS rather than a `title` attribute: the browser's own tooltip takes a
   second to appear, cannot be styled, and never shows on keyboard focus -- and the founder asked for
   a "prozorcic", which a title is not. Shown on hover AND on :focus-visible, so tabbing the palette
   teaches the same shortcuts. */
.build-chip[b-0e0y5dggtn] {
    position: relative;
}

.build-chip-key[b-0e0y5dggtn] {
    position: absolute;
    left: calc(100% + .4rem);
    top: 50%;
    z-index: 6;
    padding: .15rem .4rem;
    border: 1px solid var(--line, rgba(128, 128, 128, .35));
    border-radius: .3rem;
    background: var(--panel, #ffffff);
    color: var(--ink, #e8e8ea);
    font-size: .68rem;
    white-space: nowrap;
    opacity: 0;
    /* The tooltip must never be the click target: the chip under it is. */
    pointer-events: none;
    transform: translateY(-50%);
    transition: opacity 120ms ease-out;
}

.build-chip:hover .build-chip-key[b-0e0y5dggtn],
.build-chip:focus-visible .build-chip-key[b-0e0y5dggtn] {
    opacity: 1;
}

/* The delete tool, armed: the whole canvas says what a click will do. */
.build-chip-delete.is-armed[b-0e0y5dggtn] {
    border-color: var(--danger, #b6425a);
    color: var(--danger, #b6425a);
}

.build-canvas.is-deleting .build-node[b-0e0y5dggtn] {
    cursor: pointer;
}

.build-canvas.is-deleting .build-node:hover[b-0e0y5dggtn] {
    outline: 2px solid var(--danger, #b6425a);
    outline-offset: 2px;
}

/* A FAT INVISIBLE STROKE, and `pointer-events: stroke` on the line ITSELF -- `.build-wires` is
   pointer-events: none so the diagram does not eat clicks meant for the canvas, and a child that
   sets its own value is still hit-testable under that. Transparent rather than hidden: a
   `visibility: hidden` element is not a pointer target at all. */
.build-wire-hit[b-0e0y5dggtn] {
    stroke: transparent;
    stroke-width: 16;
    fill: none;
    cursor: pointer;
    pointer-events: stroke;
}

.build-wire-hit:hover[b-0e0y5dggtn] {
    stroke: var(--danger, #b6425a);
    stroke-opacity: .35;
}

/* BELOW 1000px THE PALETTE IS A WRAPPED ROW, so a tooltip growing rightwards from the last chip in a
   row runs off the page. Measured at 390px: the widest English one ends at x=389 of 390 -- inside by
   one pixel, which is luck rather than a design, and German ("Tastenkuerzel: J") is half again as
   long. Right-aligned underneath the chip instead, it grows LEFTWARDS and cannot reach the edge. */
@media (max-width: 1000px) {
    .build-chip-key[b-0e0y5dggtn] {
        left: auto;
        right: 0;
        top: calc(100% + .25rem);
        transform: none;
    }
}

/* NO HOVER MEANS NO TOOLTIP -- and on a touchscreen there is no keyboard to press the key with
   either, so it is hidden rather than printed on the chip. Printing it would widen every chip on the
   narrowest screen this page has, to state a shortcut that device cannot use. `aria-keyshortcuts`
   still carries the same fact to anything that reads the markup. */
@media (hover: none) {
    .build-chip-key[b-0e0y5dggtn] {
        display: none;
    }
}


/* ==== The sibling line and the divorce mark (2026-09-08) ====

   Founder, having seen his sister drawn as his partner: "treba za siblinge staviti neku posebnu
   liniju". Three line languages now, and each says what KIND of fact it is:

     solid   a partnership -- a Family row, with the double slash when it ended
     dashed  a parent-child edge -- a FamilyChild row, dot at the parent end
     dotted  siblings -- DERIVED from two FamilyChild rows and stored nowhere

   The dotted one is deliberately the quietest of the three: it is the only line on the canvas that
   is not a row somebody wrote, so it must not compete with the two that are. Muted ink rather than
   brass for the same reason -- brass means "a record" everywhere else in this application. */
.build-wire-sibling[b-0e0y5dggtn] {
    stroke: var(--ink-faint, #949cb0);
    stroke-width: 1.5;
    stroke-dasharray: 2 3;
    stroke-linecap: round;
}

/* The double slash. Danger red would read as an error; this is a fact about a marriage, not a
   warning, so it takes the same ink as the line it crosses and says its piece by shape. */
.build-wire-divorce-mark[b-0e0y5dggtn] {
    stroke: var(--accent, #b8860b);
    stroke-width: 2;
    stroke-linecap: round;
    fill: none;
}


/* ==== Half-siblings (2026-09-14) ====

   Founder: "da postoje polubraca ili polusestre pa da oznacimo nekako i to ... oznaka bude slicna
   koja je trenutno za braca i sestre, ali malo drukcija."

   Dash-DOT against the sibling line's plain dots, in the same muted ink: the two relations are the
   same KIND of fact -- derived from shared parentage, stored nowhere -- and one of them is half of
   the other, so the mark is the sibling mark with something taken out of it rather than a new
   language. Louder would be wrong: neither line is a row somebody wrote. */
.build-wire-half-sibling[b-0e0y5dggtn] {
    stroke: var(--ink-faint, #949cb0);
    stroke-width: 1.5;
    stroke-dasharray: 6 3 1 3;
    stroke-linecap: round;
    opacity: .75;
}

/* ---------------------------------------------------------------------------------------------
   "WHICH FAMILY IS THIS CHILD IN?" (2026-09-17). Same modal shell as the delete confirmation, one
   size wider, because each option is a sentence rather than a word.
   --------------------------------------------------------------------------------------------- */
.build-modal-wide[b-0e0y5dggtn] {
    max-width: 30rem;
}

.build-family-options[b-0e0y5dggtn] {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    margin: .9rem 0;
}

/* A whole row is the target, not a radio the size of a full stop: this is the question the reader
   opened the dialog to answer, and every option is one click. */
.build-family-option[b-0e0y5dggtn] {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    width: 100%;
    padding: .55rem .7rem;
    text-align: left;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    color: var(--ink);
    cursor: pointer;
    transition: border-color .12s ease, background-color .12s ease;
}

.build-family-option:hover[b-0e0y5dggtn] {
    border-color: var(--accent);
    background: color-mix(in oklab, var(--accent) 10%, transparent);
}

.build-family-option-name[b-0e0y5dggtn] {
    font-weight: 600;
}

.build-family-option-note[b-0e0y5dggtn] {
    font-size: .8125rem;
    color: var(--ink-soft);
}

/* The second question, only rendered when it is a real one -- see the markup for why Step is the
   default and why the move is not. */
.build-family-relation[b-0e0y5dggtn] {
    margin: 0 0 .8rem;
    padding: .6rem .7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}

.build-family-relation legend[b-0e0y5dggtn] {
    padding: 0 .3rem;
    font-size: .8125rem;
    color: var(--ink-soft);
}

.build-family-relation label[b-0e0y5dggtn] {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-size: .875rem;
    padding: .12rem 0;
}

/* The one that DELETES an existing link is set apart from the three that only describe. */
.build-family-move[b-0e0y5dggtn] {
    margin-top: .45rem;
    padding-top: .45rem;
    border-top: 1px solid var(--border);
}
