        /* ==========================================================================
           #N  My friends — the log's list of people
           --------------------------------------------------------------------------
           Migrated into the deck log on 2026-09-07. Drawn by public/13-friends-page.js.

           A room off the sidebar, which takes the Rooms-off-Home head: one sticky bar with
           Back at the left, the title in print caps, and at most one action at the right.
           No bottom bar — this page holds none of its four destinations.

           What it replaces: a header with a back arrow, a search field with a magnifier in
           it, two tabs carrying count badges, and rows built round a coloured disc with the
           first letter of the name in it. This world has no glyphs for actions, no badges,
           and no avatars.
           ========================================================================== */
        #friendsPage {
            background: var(--log-stock);
        }

        #friendsPage.active {
            display: flex;
            flex-direction: column;
            height: 100vh;
            height: 100dvh;
            min-height: 0;
            /* A hidden box can still be scrolled by a focused field, which pushes the head
               off the screen — the same fault the Boswell page had. */
            overflow: clip;
        }

        /* The head, its title and Back are the room head (.room-bar, css/11-home.css, since
           2026-09-16). What is this room's own: the page is a column with its own scroller,
           so the head must not shrink into it.

           ⚠️ THE RULES BELOW STILL NAME #addFriendModal, AND MUST. The Add a friend sheet
           borrows this page's head classes — its own .fr-back reads Close and its .fr-title is
           an h2 — but it is a sheet, not a room, and carries no room-* class. Only the
           #friendsPage members came off these groups; the sheet keeps every declaration it
           had. A blanket rename of .fr-back would have made the sheet's Close a room's Back. */
        #friendsPage .fr-bar {
            flex: none;
        }

        #addFriendModal .fr-title {
            margin: 0;
            font-family: var(--log-print);
            font-size: var(--fs-small);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            line-height: 1.2;
            color: var(--log-ink);
            text-align: center;
            white-space: nowrap;
        }

        #friendsPage .fr-act,
        #addFriendModal .fr-back,
        #addFriendModal .fr-act {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
            color: var(--log-ink);
            display: inline-flex;
            align-items: center;
            min-height: 44px;
        }

        #addFriendModal .fr-back { justify-self: start; }
        #friendsPage .fr-bar .fr-act { justify-self: end; }

        /* Magenta for the acts that change the record: Add sends a request, Accept makes a
           friendship, Remove ends one. Decline changes it too, but what it writes is a
           refusal, and a refusal is said quietly — so it stays ink, as it does in the
           notices. Search changes nothing and is ink. */
        #friendsPage .fr-act.is-mark,
        #addFriendModal .fr-act.is-mark { color: var(--log-magenta); }

        /* ---- the tools ---------------------------------------------------------- */

        #friendsPage .fr-tools {
            flex: none;
            background: var(--log-stock);
            border-bottom: 1px solid var(--log-rule-firm);
        }

        /* The writing line's grammar, the same as Saved's: transparent on the stock, the
           write face, the ink caret; focused, the band ground and a 2px ink baseline. */
        #friendsPage .fr-search {
            display: flex;
            align-items: center;
            padding: 0 var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
        }
        #friendsPage .fr-search:focus-within { background: var(--log-stock-lit, #DEE4E2); box-shadow: inset 0 -2px 0 0 var(--log-ink); }
        #friendsPage .fr-search-in:focus-visible { outline: none; }

        #friendsPage .fr-search-in {
            flex: 1 1 auto;
            min-width: 0;
            min-height: 44px;
            padding: 0;
            border: none;
            background: transparent;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
            caret-color: var(--log-ink);
            -webkit-appearance: none;
            appearance: none;
        }
        #friendsPage .fr-search-in::placeholder { color: var(--log-ink-soft); }
        #friendsPage .fr-search-in::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

        #friendsPage .fr-filters {
            display: flex;
            gap: 0 var(--sp-lg);
            padding: 0 var(--sp-lg);
        }

        #friendsPage .fr-filter {
            min-height: 40px;
            padding: 0;
            border: none;
            background: none;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
            cursor: pointer;
        }

        /* Two pressures of the one ink do two jobs here without colliding: `is-lit` is the
           full ink and says *there is something behind this word*; `is-on` adds the
           underline and says *this is the word you are reading*. A word can be both. */
        #friendsPage .fr-filter.is-lit { color: var(--log-ink); }
        #friendsPage .fr-filter.is-on {
            color: var(--log-ink);
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
        }

        /* ---- the rows ----------------------------------------------------------- */

        #friendsPage .fr-body {
            flex: 1;
            min-height: 0;
            display: flex;
            flex-direction: column;
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
        }

        #friendsPage .fr-count {
            margin: 0;
            padding: var(--sp-sm) var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
            border-bottom: 1px solid var(--log-rule);
        }

        /* One person is one ruled row: the name, the address under it, the acts at the
           right on the name's own line. */
        #friendsPage .fr-row {
            display: grid;
            grid-template-columns: 1fr auto;
            grid-template-rows: auto auto;
            align-items: center;
            width: 100%;
            min-height: var(--log-row);
            padding: var(--sp-xs) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
        }

        /* A name is the door to that person's page — an underlined word in the written
           voice, because it is what somebody wrote, not what the form stamped. */
        #friendsPage .fr-who {
            grid-column: 1;
            grid-row: 1;
            justify-self: start;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
            text-align: left;
            text-decoration: underline;
            text-underline-offset: 0.2em;
            text-decoration-thickness: 1px;
        }

                /* `.fr-handle`, called `.fr-mail` until 2026-09-08 — it carried an address then,
           and has carried the @handle since addresses came off this surface. A class named
           for what it no longer holds is a small lie that the next reader has to disprove. */
#friendsPage .fr-handle {
            grid-column: 1;
            grid-row: 2;
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
            overflow-wrap: anywhere;
        }

        #friendsPage .fr-tail {
            grid-column: 2;
            grid-row: 1 / -1;
            display: flex;
            align-items: center;
            gap: var(--sp-md);
            padding-left: var(--sp-md);
        }

        #friendsPage .fr-row .fr-act { min-height: 40px; }

        /* The Stamped-State Rule: what somebody already is to the reader is a word in a
           hairline box, in the softer ink, and it is content — there is nothing to press. */
        #addFriendModal .fr-stamp {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
            border: 1px solid var(--log-rule);
            padding: 2px var(--sp-xs);
            white-space: nowrap;
        }

        /* ---- the quiet lines ---------------------------------------------------- */

        #friendsPage .fr-quiet {
            margin: 0;
            padding: var(--sp-2xl) var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.5;
            color: var(--log-ink);
        }

        #friendsPage .fr-quiet span {
            display: block;
            margin-top: var(--sp-sm);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
        }

        #friendsPage .fr-quiet.is-reading { animation: wall-breathe 1.8s ease-in-out infinite; }

        /* The Ruled-To-The-Foot Rule. */
        #friendsPage .fr-rule-out {
            flex: 1;
            min-height: calc(var(--log-row) * 2);
            background-image: repeating-linear-gradient(to bottom,
                transparent 0,
                transparent calc(var(--log-row) - 1px),
                var(--log-rule) calc(var(--log-row) - 1px),
                var(--log-rule) var(--log-row));
        }

        #friendsPage :focus-visible,
        #addFriendModal :focus-visible {
            outline: 2px solid var(--log-ink);
            outline-offset: 2px;
        }

        /* ---- the Add sheet ------------------------------------------------------ */

        #addFriendModal {
            display: none;
            position: fixed;
            inset: 0;
            z-index: 3000;
            background: rgba(22, 25, 26, 0.42);
        }
        #addFriendModal.is-open { display: block; }

        /* The Plate-Not-Float Rule: it overlaps because it is a sheet laid on the page, so
           it is squared off and ruled, with no shadow and no radius. */
        #addFriendModal .fr-sheet {
            position: absolute;
            left: 0;
            right: 0;
            bottom: 0;
            max-height: 86%;
            display: flex;
            flex-direction: column;
            background: var(--log-stock);
            border-top: 1px solid var(--log-rule-firm);
        }

        #addFriendModal .fr-sheet-bar {
            flex: none;
            display: grid;
            grid-template-columns: 1fr auto 1fr;
            align-items: center;
            gap: var(--sp-sm);
            padding: var(--sp-2xs) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule-firm);
        }

        #addFriendModal .fr-sheet-body {
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
            padding-bottom: calc(var(--sp-lg) + env(safe-area-inset-bottom, 0px));
        }

        /* The ruled entry: the label in the margin column on the value's own baseline,
           never stacked above it — the Margin-Column Rule. */
        #addFriendModal .fr-entry {
            display: grid;
            grid-template-columns: 6.5rem 1fr;
            align-items: center;
            border-bottom: 1px solid var(--log-rule-firm);
        }
        #addFriendModal .fr-entry:focus-within { background: var(--log-stock-lit, #DEE4E2); box-shadow: inset 0 -2px 0 0 var(--log-ink); }

        #addFriendModal .fr-entry-label {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
            padding: var(--sp-sm) var(--sp-sm) var(--sp-sm) var(--sp-lg);
            border-right: 1px solid var(--log-rule-firm);
            align-self: stretch;
            display: flex;
            align-items: center;
        }

        #addFriendModal .fr-entry-value {
            min-height: 48px;
            padding: 0 var(--sp-lg) 0 var(--sp-sm);
            border: none;
            background: transparent;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
            caret-color: var(--log-ink);
            -webkit-appearance: none;
            appearance: none;
        }
        #addFriendModal .fr-entry-value::placeholder { color: var(--log-ink-soft); }
        #addFriendModal .fr-entry-value:focus-visible { outline: none; }
        #addFriendModal .fr-entry-value::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

        #addFriendModal .fr-sheet-acts {
            display: flex;
            justify-content: flex-end;
            gap: var(--sp-lg);
            padding: 0 var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
        }

        #addFriendModal .fr-results:empty { display: none; }

        #addFriendModal .fr-row {
            display: grid;
            grid-template-columns: 1fr auto;
            grid-template-rows: auto auto;
            align-items: center;
            min-height: var(--log-row);
            padding: var(--sp-xs) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
        }
        #addFriendModal .fr-name {
            grid-column: 1;
            grid-row: 1;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
        }
        #addFriendModal .fr-handle {
            grid-column: 1;
            grid-row: 2;
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
            overflow-wrap: anywhere;
        }
        #addFriendModal .fr-tail {
            grid-column: 2;
            grid-row: 1 / -1;
            display: flex;
            align-items: center;
            padding-left: var(--sp-md);
        }

        /* The invitation itself, when the clipboard refuses: a thing to be selected and
           copied by hand, so it wraps rather than truncating and it is not the written voice
           — it is a machine's string, and it should look like one. */
        #addFriendModal .fr-link {
            display: block;
            margin-top: var(--sp-sm);
            font-family: var(--log-print);
            font-size: var(--fs-small);
            color: var(--log-ink);
            overflow-wrap: anywhere;
            user-select: all;
        }

        #addFriendModal .fr-quiet {
            margin: 0;
            padding: var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.5;
            color: var(--log-ink);
        }
        #addFriendModal .fr-quiet.is-reading { animation: wall-breathe 1.8s ease-in-out infinite; }
