// ── Bud btn (bottom-left mirror of #id_kit_btn) ───────────────────────── // // Lives on post.html only — slide-out recipient field for the share-post // async flow. Mutually exclusive w. #id_kit_btn (bottom-right): when one is // active (.active class on btn + html.{kit|bud}-open class on root), the // other quickly fades to opacity 0. // // Spec: functional_tests/test_bud_btn.py. #id_bud_btn { position: fixed; bottom: 0.5rem; left: 0.5rem; // In landscape, the bud-btn lives at the BOTTOM-RIGHT corner of the // right sidebar (mirror of kit_btn at the TOP). Burger sits to its // LEFT in the same horizontal pair (kit/gear at top, bud/burger at // bottom). Anchored at right: 0.5rem (same as portrait) so the // burger-to-the-LEFT-of-bud gap reads as 0.7rem edge-to-edge, // matching the portrait burger-above-bud gap. @media (orientation: landscape) { left: auto; right: 0.5rem; top: auto; bottom: 0.5rem; } z-index: 318; font-size: 1.75rem; cursor: pointer; color: rgba(var(--secUser), 1); display: inline-flex; align-items: center; justify-content: center; width: 3rem; height: 3rem; border-radius: 50%; background-color: rgba(var(--priUser), 1); border: 0.15rem solid rgba(var(--secUser), 1); transition: opacity 0.15s ease; &.active { color: rgba(var(--quaUser), 1); border-color: rgba(var(--quaUser), 1); } } // Slide-out panel: collapsed by default; opens to span ~viewport - 3rem. #id_bud_panel { position: fixed; bottom: 0.5rem; // align bottom edge w. bud btn left: 1.5rem; right: 1.5rem; height: 3rem; // match bud btn height for vertical-centre alignment z-index: 317; display: flex; align-items: center; gap: 0.5rem; pointer-events: none; overflow: hidden; // Closed state — collapse leftward into the bud btn transform-origin: left center; transform: scaleX(0); transition: transform 0.2s ease-out, opacity 0.15s ease; opacity: 0; @media (orientation: landscape) { // Bud-btn lives at the BOTTOM of the right sidebar in landscape, // so the panel slides out leftward from the right edge along the // BOTTOM. Clear both fixed sidebars; transform-origin flips to // right so the closed state collapses into the bud-btn. top: auto; bottom: 0.5rem; left: calc(var(--sidebar-w) + 0.5rem); right: calc(var(--sidebar-w) + 0.5rem); transform-origin: right center; } #id_recipient { flex: 1; min-width: 0; height: 100%; // Generous left padding so the bud btn glyph (3rem circle pinned // at left:1.5rem) doesn't visually overlap the placeholder/typed text. padding: 0 1rem 0 3.5rem; background-color: rgba(var(--priUser), 1); color: rgba(var(--secUser), 1); border: 0.1rem solid rgba(var(--secUser), 0.5); border-radius: 1.5rem; font-family: inherit; &:focus { outline: none; border-color: rgba(var(--terUser), 0.75); box-shadow: 0 0 0.75rem rgba(var(--terUser), 0.5); } } .btn.btn-confirm { flex-shrink: 0; } } // html.bud-open: slide the panel out, fade the kit btn away. // Kit fade is PORTRAIT-only: in landscape kit lives at the TOP of the // right sidebar + bud_panel sits at the BOTTOM (full-width strip), so // they don't visually compete + kit can stay visible. html.bud-open { #id_bud_panel { transform: scaleX(1); opacity: 1; pointer-events: auto; } @media (orientation: portrait) { #id_kit_btn { opacity: 0; pointer-events: none; } } } // Kit dialog open: hide the bud btn. We don't add an `html.kit-open` // class (game-kit.js uses [open] on the dialog + .active on the btn), so // the mutual-exclusion is driven by `:has()` against the open dialog. html:has(#id_kit_bag_dialog[open]) #id_bud_btn { opacity: 0; pointer-events: none; } // ── Bud autocomplete suggestions (mirror of sky-place birth picker) ── // Sibling of #id_bud_panel (which has overflow:hidden for the scaleX // slide animation, so the suggestions can't be a child or they'd clip). // Position-fixed; portrait sits ABOVE the panel (panel at bottom), // landscape sits BELOW the panel (panel at top of viewport). .bud-suggestions { position: fixed; bottom: 4rem; // panel bottom (0.5rem) + height (3rem) + gap (0.5rem) left: 1.5rem; right: 1.5rem; z-index: 320; // above the panel itself background: rgba(var(--priUser), 1); border: 0.1rem solid rgba(var(--terUser), 0.3); border-radius: 0.3rem; overflow-y: auto; max-height: 10rem; box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.4); @media (orientation: landscape) { // Panel sits at the BOTTOM in landscape, so suggestions rise upward // from above the panel — shadow direction stays "up" (negative y). top: auto; bottom: 4rem; box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.4); left: calc(var(--sidebar-w) + 0.5rem); right: calc(var(--sidebar-w) + 0.5rem); } } .bud-suggestion-item { display: block; width: 100%; padding: 0.4rem 0.6rem; text-align: left; background: none; border: none; border-bottom: 0.05rem solid rgba(var(--terUser), 0.1); font-size: 0.85rem; color: rgba(var(--ninUser), 0.85); cursor: pointer; line-height: 1.35; &:last-child { border-bottom: none; } &:hover, &:focus { background: rgba(var(--terUser), 0.12); color: rgba(var(--ninUser), 1); outline: none; } } // ── Duplicate-add highlight ───────────────────────────────────────────── // // Eased-in flash applied by Brief.showDuplicateBanner's FYI button to a // caller-supplied target element — one of .bud-entry .bud-name (My Buds), // .post-recipient (post share), or .gate-slot.filled (gatekeeper invite). // Class is auto-removed by note.js 3s after FYI; SCSS transition handles // both the ease-in (on add) AND the ease-out (on remove). Palette swap // vs. the original spec: color = --ninUser, text-shadow = --terUser. .bud-duplicate-flash { color: rgba(var(--ninUser), 1); text-shadow: 0 0 0.5em rgba(var(--terUser), 1); transition: color 600ms ease, text-shadow 600ms ease; }