/* ============================================================
   AEGIS app and composed surfaces.
   Ported from aegis-dashboard.html: the app shell, views, stat
   cards, rule cards, settings panel, audit table, reaction-role
   and command cards, placeholder, toasts, and the responsive
   layer. Class names are kept identical to the dashboard so this
   stays a faithful mirror.
   ============================================================ */

/* ---------- app shell ---------- */
.app { display: flex; flex-direction: column; min-height: 100vh; width: 100%; --sidebar-w: 0px; --content-w: 76rem; }
.mobile-bar { display: flex; align-items: center; justify-content: space-between; padding: 1.25rem; background: var(--base); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); position: relative; z-index: 30; }
.mobile-bar__brand { display: flex; align-items: center; gap: 0.75rem; }
.mobile-bar .brand-mark { width: 40px; height: 40px; border-radius: 12px; font-size: 18px; color: var(--accent); }
.mobile-bar h1 { font-size: 20px; }
.menu-btn { width: 48px; height: 48px; border-radius: 12px; font-size: 18px; }

.overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(4px); z-index: 40; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; }
.overlay.is-visible { opacity: 1; visibility: visible; }

/* One merged icon sidebar (041): the picked server at the top, icon-only nav, the user at the foot. It is
   icon-only by default; the hamburger expands it to a labelled bar (.app.nav-expanded). */
/* Content is left-anchored in both states so collapsing just contracts the bar from the right, without
   shifting the icons and avatar toward the (still-wide) center and back (042 feedback). */
.sidebar { position: relative; z-index: 2; width: 88px; flex-shrink: 0; height: 100vh; display: flex; flex-direction: column; align-items: flex-start; gap: 0.75rem; padding: 1.25rem 1rem; background: var(--base); box-shadow: 4px 0 24px rgba(0, 0, 0, 0.12); }
/* Match the nav-item box (56px) and centre the hamburger in it, the same as the Rules/Log/Settings icons
   which are symmetric in their 56px button, so its centre lines up with theirs in both states (065). */
.sidebar__toggle { width: 56px; height: 56px; border-radius: 18px; border: none; background: transparent; color: var(--text-muted); cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.sidebar__toggle:hover { color: var(--text-main); }
/* The hamburger strokes sit inside the viewBox with padding, so the box is sized up well past the 22px
   nav icon to render at a matching visible size (065). */
.sidebar__toggle .ham { width: 40px; height: 40px; }
.sidebar__server { display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem; width: 100%; flex-shrink: 0; overflow: hidden; }
/* The name is hidden when collapsed (icon only) and shown when expanded or on phones. Never selectable. */
.sidebar__server-name { display: none; -webkit-user-select: none; user-select: none; font-size: 14px; line-height: 1.2; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar__divider { width: 44px; height: 4px; border-radius: 2px; background: rgba(0, 0, 0, 0.2); box-shadow: var(--pressed-sm); flex-shrink: 0; }
.sidebar__nav { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; overflow-y: auto; overflow-x: hidden; min-height: 0; }
.sidebar__item { width: 100%; display: flex; justify-content: flex-start; }
.sidebar__foot { margin-top: auto; width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; justify-content: flex-start; padding-top: 0.75rem; flex-shrink: 0; }
/* The persistent sign-out icon below the avatar. It clips (so its "Sign out" label is revealed by the bar's
   width animation instead of popping in ahead of the others); the shared gutter rule below keeps its hover
   shadow from being sliced. */
.sidebar__signout { display: flex; overflow: hidden; }
/* Icon nav button: icon-only on desktop (the label is a hover tooltip), full row with the label on phones. */
/* The icon is placed by a fixed left padding (not by centering-in-the-button), so its x never depends on the
   button or bar width, and it stays put while the bar expands/collapses. padding-left ~= (56-22)/2 centers it. */
.nav-icon-btn { position: relative; width: 56px; height: 56px; flex-shrink: 0; border-radius: 18px; display: flex; align-items: center; justify-content: flex-start; padding-left: 17px; color: var(--text-muted); text-decoration: none; border: none; background: transparent; cursor: pointer; transition: color 0.2s ease, box-shadow 0.2s ease; }
.nav-icon-btn:hover { color: var(--text-main); box-shadow: var(--raised-sm); }
.nav-icon-btn.active { color: var(--accent); box-shadow: var(--pressed-key); }
/* The icon never shrinks and the label never wraps, so the bar width can animate without resizing the icon
   or reflowing the label onto extra lines (the label just clips as the bar narrows). */
.nav-icon-btn .nav-icon { width: 22px; height: 22px; flex-shrink: 0; }
.nav-icon-btn__label { display: none; white-space: nowrap; }
/* Collapsed-rail hover tooltip (desktop, icon-only state): float the item's own label to the right of its
   icon as an elevated chip, so you can read what an icon is without expanding the bar. Absolutely positioned
   and pointer-transparent, so it overlays the content column and never displaces anything. Only in the
   collapsed state (the expanded bar shows labels inline) and not on phones (the menu is a full labelled list). */
@media (min-width: 768px) {
    .app:not(.nav-expanded) .nav-icon-btn:hover .nav-icon-btn__label {
        display: block; position: absolute; left: calc(100% + 10px); top: 50%; transform: translateY(-50%);
        background: var(--base); color: var(--text-main); box-shadow: var(--raised-sm), 0 6px 18px rgba(0, 0, 0, 0.22);
        padding: 0.35rem 0.65rem; border-radius: 10px; font-size: 13px; font-weight: 500; line-height: 1.2;
        z-index: 40; pointer-events: none;
    }
}
.nav-icon-btn.is-locked { opacity: 0.45; cursor: not-allowed; }
.nav-icon-btn.is-locked:hover { color: var(--text-muted); box-shadow: none; }
.nav-icon-btn .nav-item__lock { position: absolute; top: 6px; right: 6px; width: 12px; height: 12px; }
/* The user chip at the foot. Collapsed: avatar only, hovering reveals the card (name + sign out) to the
   right. Expanded and on phones: the card sits inline next to the avatar. */
/* The user chip stays inside the bar and clips as it narrows (like the nav labels), instead of the name card
   jumping outside the bar on collapse. */
.user-chip { position: relative; display: flex; justify-content: flex-start; align-items: center; gap: 0.6rem; width: 100%; overflow: hidden; }
.user-chip__avatar { width: 44px; height: 44px; border-radius: 22px; object-fit: cover; box-shadow: var(--raised-sm); cursor: pointer; flex-shrink: 0; -webkit-user-select: none; user-select: none; }
.user-chip__avatar--mono { display: flex; align-items: center; justify-content: center; background: var(--accent-deep); color: #fff; font-weight: 700; }
/* The name card is hidden when the bar is collapsed (like the nav labels and server name) rather than relying
   on the chip's clip, so the shadow gutter can give the avatar room on the right without exposing a sliver of
   the name. Shown when the bar is expanded or on phones. */
.user-chip__card { display: none; align-items: center; gap: 0.6rem; min-width: 0; flex: 1; }
.user-chip__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-main); font-weight: 500; -webkit-user-select: none; user-select: none; background: var(--base); box-shadow: var(--raised-sm); border-radius: 10px; padding: 0.3rem 0.65rem; }
/* Style-wide shadow safe-area: every sidebar container that clips its overflow (to reveal labels or the name
   as the bar collapses) widens its clip box by --shadow-gutter on all sides. The negative margin cancels the
   padding for layout, so occupied size, icon/avatar positions, gaps, and header/foot pinning are unchanged,
   only the clip region grows into the surrounding transparent space, so no child's raised shadow is ever cut
   (the nav buttons, the sign-out, the server icon, and the name card all get the same breathing room). */
.sidebar__server, .sidebar__nav, .sidebar__signout, .user-chip {
    width: calc(100% + var(--shadow-gutter) * 2);
    margin: calc(var(--shadow-gutter) * -1);
    padding: var(--shadow-gutter);
}
/* A username rendered as a raised clay chip (elevated like a button), used inline in prose too. */
.name-chip { display: inline-block; background: var(--base); box-shadow: var(--raised-sm); border-radius: 10px; padding: 0.15rem 0.6rem; color: var(--text-main); font-weight: 600; -webkit-user-select: none; user-select: none; }
/* Action-log time: shown in the viewer's timezone, click to copy a Discord timestamp. */
.log-time { background: transparent; border: none; padding: 0; font: inherit; color: inherit; cursor: pointer; -webkit-user-select: none; user-select: none; }
.log-time:hover { color: var(--text-main); text-decoration: underline dotted; }
/* A failed moderation action (028): shown with a small red badge next to the action type in the log. */
.badge-failed { margin-left: 0.5rem; padding: 0.05rem 0.4rem; border-radius: 6px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--red); box-shadow: var(--pressed-sm); }
/* Action-log Target/Moderator: a readable display name (043) with a small copy-id marker that copies the
   exact numeric id. The name truncates with the full value on hover; the marker is clearly a control. */
.id-cell { display: inline-flex; align-items: center; gap: 0.4rem; max-width: 100%; min-width: 0; }
.id-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 14rem; }
.id-copy { flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; border: none; border-radius: 8px; background: transparent; color: var(--text-darker); cursor: pointer; transition: color 0.15s ease, box-shadow 0.15s ease; }
.id-copy .ic { width: 14px; height: 14px; }
.id-copy:hover { color: var(--accent); box-shadow: var(--raised-sm); }
.id-copy:active { box-shadow: var(--pressed-key); }
/* The "Copied timestamp!" confirmation: a small clay bubble that floats up and fades, shown over the clicked
   time so the time column never changes width. Fixed to the viewport, non-interactive, short-lived. */
.copied-bubble { position: fixed; z-index: 100; pointer-events: none; background: var(--base); color: var(--accent); box-shadow: var(--raised-sm); border-radius: 10px; padding: 0.3rem 0.6rem; font-size: 13px; font-weight: 600; white-space: nowrap; opacity: 0; transform: translate(-50%, -100%) translateY(6px); transition: opacity 0.18s ease, transform 0.26s ease; }
.copied-bubble.is-shown { opacity: 1; transform: translate(-50%, -100%) translateY(-4px); }
/* Expanded desktop bar: wider, labelled, with the server name and user card shown inline. The width slide
   is gated (.nav-ready) and lives in the desktop media block, so it never governs the phone menu. */
.app.nav-expanded .sidebar { width: 240px; align-items: stretch; }
.app.nav-expanded .sidebar__toggle { align-self: flex-start; }
/* The shared gutter keeps the server icon at the same x in both states (the negative margin cancels the
   padding), so expanding only needs to switch the layout to a row, no padding override. */
.app.nav-expanded .sidebar__server { flex-direction: row; align-items: center; gap: 0.6rem; }
.sidebar__server .server-icon { flex-shrink: 0; }

/* Desktop only: centre the smaller rail elements on the nav-icon column line (44px), so the whole rail
   lines up. The server icon (48px) and the divider and avatar (44px) are narrower than the 56px nav
   button, so they need a small offset. On phones the menu is a full-width list, so no offset (065). */
@media (min-width: 768px) {
    .sidebar__server .server-icon { margin-left: 4px; }
    .sidebar__divider { margin-left: 6px; }
    .user-chip__avatar { margin-left: 6px; }
}
.app.nav-expanded .sidebar__server-name { display: block; text-align: left; }
.app.nav-expanded .sidebar__nav { align-items: stretch; }
.app.nav-expanded .sidebar__item { justify-content: stretch; }
.app.nav-expanded .nav-icon-btn { width: 100%; height: 56px; justify-content: flex-start; gap: 1rem; padding: 0 1rem 0 17px; }
.app.nav-expanded .nav-icon-btn__label { display: inline; font-size: 14px; }
.app.nav-expanded .nav-icon-btn .nav-item__lock { position: static; margin-left: auto; }
/* No element (button, link, icon, avatar) is draggable, so pressing one never starts a ghost drag. */
.aegis button, .aegis a, .aegis img, .aegis [role="button"], .aegis .nav-icon-btn, .aegis .btn,
.aegis .server-icon, .aegis .server-card, .aegis .user-chip__avatar, .aegis .nav-icon { -webkit-user-drag: none; }
.app.nav-expanded .user-chip { justify-content: flex-start; gap: 0.6rem; overflow: hidden; }
.app.nav-expanded .user-chip__card { display: flex; min-width: 0; }
.avatar { width: 48px; height: 48px; border-radius: 50%; padding: 4px; background: var(--base); box-shadow: var(--raised-sm); cursor: pointer; }
.avatar img { width: 100%; height: 100%; border-radius: 50%; display: block; }

/* Server icons are circles, like profile pictures (not the squircle-morph). */
.server-icon { width: 48px; height: 48px; border-radius: 50%; background: var(--base); box-shadow: var(--raised-sm); display: flex; align-items: center; justify-content: center; color: var(--text-muted); font-weight: 400; cursor: pointer; border: none; font-family: inherit; transition: color 0.3s ease, box-shadow 0.3s ease; }
.server-icon:hover { color: var(--text-main); box-shadow: var(--raised); }
.server-icon:active { box-shadow: var(--pressed-key); }
/* Elevated (clay) like a button, not pressed, so the picked server icon feels raised off the plane. */
.server-icon.is-active { border-radius: 50%; box-shadow: var(--raised); color: var(--accent); }
.server-icon--add { color: var(--accent); margin-top: 0.5rem; }
.server-icon--muted { color: var(--text-darker); }
/* The picked server's real icon fills the rail marker (037), monogram fallback. */
.server-icon__img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
.server-icon__mono { font-weight: 600; font-size: 18px; }
/* The sidebar server row is a link back to the server list. A picked server reveals a switch icon over its
   icon on hover, so it can be swapped in one click. With no server picked, the whole row is the call to action. */
.sidebar__server { text-decoration: none; color: inherit; cursor: pointer; }
.sidebar__server .server-icon { position: relative; }
.server-icon__switch { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; border-radius: inherit; background: color-mix(in srgb, var(--base) 82%, transparent); color: var(--accent); opacity: 0; transition: opacity 0.15s ease; pointer-events: none; }
.server-icon__switch .ic { width: 20px; height: 20px; }
.sidebar__server--picked:hover .server-icon__switch { opacity: 1; }

/* The signed-in user panel (037), pinned to the bottom of the nav sidebar (Discord's user-panel spot). */
.user-panel { margin-top: auto; display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0.75rem; border-radius: 14px; background: var(--base); box-shadow: var(--raised-sm); }
.user-panel__avatar { width: 40px; height: 40px; border-radius: 20px; object-fit: cover; flex-shrink: 0; box-shadow: var(--raised-sm); }
.user-panel__avatar--mono { display: flex; align-items: center; justify-content: center; background: var(--accent-deep); color: #fff; font-weight: 700; }
.user-panel__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-main); font-weight: 500; }
.user-panel__signout { margin: 0; flex-shrink: 0; }
.user-panel__signout-btn { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: 10px; background: transparent; color: var(--text-darker); cursor: pointer; transition: color 0.2s ease, box-shadow 0.2s ease; }
.user-panel__signout-btn:hover { color: var(--accent); box-shadow: var(--raised-sm); }
.user-panel__signout-btn:active { box-shadow: var(--pressed-key); }

/* Phone top app bar (hidden on desktop): holds the menu button in its own space so it never overlaps content. */
.topbar { display: none; }
.topbar__menu { width: 44px; height: 44px; border: none; border-radius: 12px; background: transparent; color: var(--text-main); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.topbar__menu:hover { color: var(--accent); }
.topbar__brand { font-size: 18px; font-weight: 500; }

/* Animated hamburger (065): three strokes that curl and rotate into an X when the menu is open. Driven
   off .app.nav-open, no per-icon script. Only ever visible in the phone top bar. */
.ham { width: 30px; height: 30px; transition: transform 0.4s ease; }
.ham__line { fill: none; stroke: var(--text-main); stroke-width: 5.5; stroke-linecap: round; transition: stroke-dasharray 0.4s, stroke-dashoffset 0.4s, stroke 0.2s ease; }
.ham__top, .ham__bot { stroke-dasharray: 40 121; }
.topbar__menu:hover .ham__line, .sidebar__toggle:hover .ham__line { stroke: var(--accent); }
.app.nav-open .ham { transform: rotate(45deg); }
.app.nav-open .ham__top, .app.nav-open .ham__bot { stroke-dashoffset: -68px; }
.app.nav-open .ham__line { stroke: var(--accent); }

/* Desktop: the sidebar rows assemble in with the same stagger, as a load entrance (065). The persistent
   layout means it plays on a full load, not on every in-app navigation. */
@keyframes nav-in { from { opacity: 0; transform: translateX(-16px); } to { opacity: 1; transform: none; } }
@media (min-width: 768px) {
    .sidebar__server, .sidebar__nav .sidebar__item, .sidebar__foot { animation: nav-in 0.42s cubic-bezier(0.4, 0, 0.2, 1) both; }
    .sidebar__server { animation-delay: 0.04s; }
    /* The stagger is driven by each row's --nav-i (set in NavMenu), so adding a nav entry keeps the
       cascade in step without touching this rule. */
    .sidebar__nav .sidebar__item { animation-delay: calc(0.10s + var(--nav-i, 0) * 0.05s); }
    .sidebar__foot { animation-delay: 0.42s; }
}

/* The desktop collapse toggle uses the same morphing hamburger, curling into an X when the bar is expanded. */
.app.nav-expanded .sidebar__toggle .ham { transform: rotate(45deg); }
.app.nav-expanded .sidebar__toggle .ham__top, .app.nav-expanded .sidebar__toggle .ham__bot { stroke-dashoffset: -68px; }
.app.nav-expanded .sidebar__toggle .ham__line { stroke: var(--accent); }

/* Respect reduced motion: the hamburger and the menu rows still change state, just without the animation. */
@media (prefers-reduced-motion: reduce) {
    .ham, .ham__line, .sidebar__server, .sidebar__item, .sidebar__foot { transition: none; animation: none; }
}
/* Back-to-top: fades in when scrolling up (away from the top), fades out on scroll-down or at the top. */
.to-top { position: fixed; right: 20px; bottom: 20px; z-index: 90; width: 48px; height: 48px; border: none; border-radius: 50%; background: var(--base); box-shadow: var(--raised-sm), 0 8px 20px rgba(0, 0, 0, 0.28); color: var(--accent); cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transform: translateY(10px); transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s; }
.to-top.is-visible { opacity: 1; visibility: visible; transform: translateY(0); }
.to-top:hover { color: var(--text-main); }
/* Blank round buttons (the sidebar server icons and avatars) take the firmer blank-disc edge, so a plain
   disc with no glyph still clearly reads as elevated (aegis.css --raised-blank). Other circles stay soft. */
.avatar, .server-icon, .server-icon.is-active { box-shadow: var(--raised-blank); }
/* Interactive, non-input elements do not select their text on click or drag (inputs keep selection). */
.aegis button, .aegis .btn, .aegis label, .aegis [role="button"],
.aegis .nav-icon-btn, .aegis .topbar__menu, .aegis .server-icon, .aegis .server-icon__mono,
.aegis .server-card, .aegis .reference__toc a, .aegis .add-row, .aegis .picker__link, .aegis .picker__opt,
.aegis .dismiss, .aegis .user-panel__signout-btn, .aegis .stepper button {
    -webkit-user-select: none; user-select: none;
}
/* Lock down the chrome: labels, headings, eyebrows, and instructional/summary text are not selectable, so
   only real content (field values, message text, ids in the log) can be selected and copied. */
.aegis .label, .aegis .eyebrow, .aegis .crumb-current, .aegis .view-title, .aegis .view-sub,
.aegis .section-title, .aegis .nav-group__label, .aegis th, .aegis .segmented, .aegis .segmented__opt,
.aegis .rule__state, .aegis .meta-line, .aegis .placeholder-icon, .aegis .toggle,
.aegis h1, .aegis h2, .aegis h3, .aegis h4 {
    -webkit-user-select: none; user-select: none;
}
/* The picker search gets a clear button; the blocked enable button shows it is not clickable. */
.picker__filter-wrap { position: relative; flex: 1; display: flex; }
.picker__filter-wrap .picker__filter { flex: 1; padding-right: 2rem; }
.picker__filter-clear { position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; padding: 0; border: none; background: transparent; color: var(--text-darker); cursor: pointer; display: flex; align-items: center; justify-content: center; -webkit-user-select: none; user-select: none; }
.picker__filter-clear:hover { color: var(--text-main); }
.btn.is-blocked { cursor: not-allowed; }
/* Phone: the sidebar becomes a full-screen menu (with labels) opened from the top bar, self-closing on pick. */
@media (max-width: 767.98px) {
    /* Above the open full-screen menu (z 56), so the morphed X in the bar stays tappable to close it (065). */
    .topbar { display: flex; align-items: center; gap: 0.5rem; position: fixed; top: 0; left: 0; right: 0; height: 56px; padding: 0 0.75rem; background: var(--base); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); z-index: 60; }
    /* The phone menu is hidden by default (off-screen left) and shown by adding .nav-open. Its own always-on
       transition is not gated by .nav-ready, so an enhanced-navigation class wipe can never make it instant. */
    .sidebar { position: fixed; inset: 0; width: 100vw; height: 100%; flex-direction: column; align-items: stretch; gap: 0.5rem; padding: 4.5rem 1.25rem 1.25rem; z-index: 56; transform: translateX(-100%); opacity: 0; pointer-events: none; transition: transform 0.42s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.38s ease; }
    .sidebar__toggle { display: none; }
    .sidebar__server { flex-direction: row; align-items: center; gap: 0.75rem; }
    .sidebar__server-name { display: block; font-size: 16px; text-align: left; }
    .sidebar__nav { align-items: stretch; }
    .sidebar__item { justify-content: stretch; }
    .nav-icon-btn { width: 100%; height: auto; justify-content: flex-start; gap: 1rem; padding: 0.85rem 1rem; border-radius: 12px; }
    .nav-icon-btn__label { display: inline; font-size: 15px; }
    .nav-icon-btn .nav-item__lock { position: static; margin-left: auto; }
    .user-chip { justify-content: flex-start; gap: 0.6rem; }
    .user-chip__card { display: flex; min-width: 0; }
    /* Open slides in a touch quicker; the base (closed) transition above is slightly slower so the menu
       slides away gently while the new page paints behind it (picking a page removes nav-open). */
    .app.nav-open .sidebar { transform: translateX(0); opacity: 1; pointer-events: auto; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s ease; }
    /* The menu's rows start shifted and faded, then assemble in with a stagger as it opens (065). The delays
       live only on the .nav-open selectors, so closing is a clean, undelayed fade with the slide-away. */
    .sidebar__server, .sidebar__item, .sidebar__foot { opacity: 0; transform: translateX(-16px); transition: opacity 0.3s ease, transform 0.36s cubic-bezier(0.4, 0, 0.2, 1); }
    .app.nav-open .sidebar__server { opacity: 1; transform: none; transition-delay: 0.04s; }
    .app.nav-open .sidebar__item { opacity: 1; transform: none; }
    .app.nav-open .sidebar__nav .sidebar__item { transition-delay: calc(0.10s + var(--nav-i, 0) * 0.05s); }
    .app.nav-open .sidebar__foot { opacity: 1; transform: none; transition-delay: 0.42s; }
    .main { width: 100%; overflow-x: hidden; }
    /* Higher specificity than the base .view-body padding so the fixed top bar never overlaps the page. */
    .main .view-body { padding-top: calc(56px + 1rem); }
    .reference__table { display: block; overflow-x: auto; }
}

/* ---------- main and views ---------- */
/* Pad the right by the sidebar width so .main's content box is symmetric around the viewport (the in-flow
   sidebar insets the left by the same amount), letting centered content sit at true screen-center. */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; padding-right: var(--sidebar-w); transition: padding-right 0.28s cubic-bezier(0.4, 0, 0.2, 1); }
.view-header { padding: 1.5rem; }
.view-header__row { display: flex; flex-direction: column; gap: 1.5rem; }
.view-title { font-size: 30px; }
.view-title-row { display: flex; align-items: center; gap: 1.25rem; margin-top: 0.75rem; }
.view-sub { color: var(--text-muted); font-size: 16px; margin-top: 0.75rem; max-width: 36rem; line-height: 1.6; }
/* The whole app's content sits in a centered max-width column (--content-w), like a docs site, so wide screens
   frame it with whitespace. .main pads its right by the sidebar width (below), making its content box symmetric
   around the viewport, so this simple auto-margin centering lands the column dead-center on screen with the
   icon bar in the left whitespace. It fills the space on narrow screens and stacks on phones. */
.view-body { flex: 1; width: 100%; max-width: var(--content-w); margin-inline: auto; padding: 1rem 1.5rem 3rem; }

/* Legal footer: quiet Terms and Privacy links centered at the bottom of the main body on every page,
   small and low-contrast, there for those who look and out of the way otherwise. */
.app-footer { padding: 1.5rem; text-align: center; font-size: 12px; color: var(--text-darker); }
.app-footer a { color: inherit; text-decoration: none; }
.app-footer a:hover { color: var(--text-muted); text-decoration: underline; }
.app-footer__sep { margin: 0 0.4rem; opacity: 0.5; }
.view-actions { display: flex; flex-wrap: wrap; gap: 1rem; }

.unsaved { margin-bottom: 2rem; padding: 1rem 1.5rem; display: flex; flex-direction: column; gap: 1rem; }
.unsaved__msg { display: flex; align-items: center; gap: 1rem; color: var(--accent); font-weight: 400; }
.unsaved__actions { display: flex; gap: 1rem; }

/* ---------- stats ---------- */
.stats { display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-bottom: 2.5rem; }
.stat { display: flex; align-items: center; gap: 1.25rem; padding: 1.5rem; }
.stat__icon { width: 56px; height: 56px; border-radius: 16px; font-size: 24px; color: var(--text-muted); flex-shrink: 0; }
.stat__icon, .rule__icon, .event__icon, .placeholder-icon, .embed-row__icon {
    /* no container box (an inset box would read as "selected"). The glyph itself is engraved into the
       flat surface it sits on, the carve is the default on every .ic (see aegis.css). */
    display: flex; align-items: center; justify-content: center;
    background: transparent; box-shadow: none;
}
.stat__value { font-size: 30px; font-weight: 700; }
.stat--meter { flex-direction: column; align-items: stretch; justify-content: center; }
.meter-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.meter-row { display: flex; align-items: center; gap: 1rem; }
.cursor-help { cursor: help; color: var(--text-muted); }

/* ---------- rule summary card ---------- */
.rules { display: grid; grid-template-columns: 1fr; gap: 2rem; }
.rules-col { display: flex; flex-direction: column; gap: 1.5rem; }
.section-title { font-size: 20px; margin-bottom: 0.5rem; }
.rule { padding: 1.5rem; }
.rule.is-disabled { opacity: 0.42; }
.rule__head { display: flex; flex-direction: column; gap: 1.5rem; }
.rule__id { display: flex; align-items: center; gap: 1.25rem; }
.rule__icon { width: 48px; height: 48px; border-radius: 16px; font-size: 20px; flex-shrink: 0; }
.rule__icon--danger { color: var(--text-muted); }
.rule__icon--muted { color: var(--text-darker); }
/* The in-app reference (035): sidebar of sections + generated content. */
.reference { display: block; margin-top: 1.5rem; }
.reference__nav { position: sticky; top: 2rem; flex: 0 0 12rem; display: flex; flex-direction: column; gap: 0.75rem; }
.reference__filter { width: 100%; }
.reference__toc { display: flex; flex-direction: column; gap: 2px; padding: 0.5rem; border-radius: 12px; box-shadow: var(--inset-well, var(--pressed)); }
.reference__toc a { color: var(--text-muted); text-decoration: none; padding: 6px 10px; border-radius: 8px; font-size: 14px; }
.reference__toc a:hover { color: var(--text-main); box-shadow: var(--raised-sm); }
/* The section you are currently scrolled to is highlighted in white with a pressed look (041, 023). */
.reference__toc a.is-active { color: var(--text-main); box-shadow: var(--pressed); }
/* A query container so the tables inside can respond to the real content width (which the sidebar and
   the section nav both eat into), not the window width. This is what makes them right at every size. */
.reference__content { min-width: 0; display: flex; flex-direction: column; gap: 2.5rem; container-type: inline-size; }
.reference__content section { scroll-margin-top: 2rem; }
/* Anything the reader might want to quote lands on an anchor, so the heading never sits flush to the top edge. */
.reference__content [id] { scroll-margin-top: 2rem; }

/* A guide body is real content, not chrome: unlock selection so a reader can copy a heading, a sentence, a
   pattern, or a table cell straight out of a guide. This deliberately overrides the site-wide chrome lockdown
   further up, scoped to the docs body only, so the nav rail, breadcrumbs, and pager stay unselectable. Each
   locked class is named explicitly, because inheritance alone loses to those more specific lockdown rules. */
.aegis .docs-main > .view-title,
.aegis .docs-main .reference__content,
.aegis .docs-main .reference__content h1, .aegis .docs-main .reference__content h2,
.aegis .docs-main .reference__content h3, .aegis .docs-main .reference__content h4,
.aegis .docs-main .reference__content p, .aegis .docs-main .reference__content li,
.aegis .docs-main .reference__content td, .aegis .docs-main .reference__content th,
.aegis .docs-main .reference__content span, .aegis .docs-main .reference__content strong,
.aegis .docs-main .reference__content .section-title, .aegis .docs-main .reference__content .view-sub,
.aegis .docs-main .reference__content .mono {
    -webkit-user-select: text; user-select: text;
}

/* Every heading carries a "#" that fades in on hover (or keyboard focus) and links to that section, so a reader
   can hand someone a link that lands on the exact heading. It is chrome, so it never joins a text selection. */
.aegis .heading-anchor {
    margin-left: 0.5rem; color: var(--text-darker); text-decoration: none; font-weight: 400;
    opacity: 0; transition: opacity 0.15s ease, color 0.15s ease;
    -webkit-user-select: none; user-select: none;
}
.reference__content h2:hover .heading-anchor,
.reference__content h3:hover .heading-anchor,
.aegis .heading-anchor:focus-visible { opacity: 1; }
.aegis .heading-anchor:hover { color: var(--accent); }
.reference__kind { padding: 1rem 0; border-top: 2px solid var(--shadow-d); }
.reference__kind-name { font-size: 16px; margin: 0; color: var(--text-main); }
.reference__none { font-size: 13px; color: var(--text-darker); margin: 0.25rem 0; }
.reference__eg { font-size: 13px; margin: 0.25rem 0; color: var(--teal); }
.reference__eg-tag { color: var(--text-darker); text-transform: uppercase; font-size: 11px; letter-spacing: 0.06em; }
.reference__table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; font-size: 14px; }
.reference__table td, .reference__table th { text-align: left; padding: 6px 10px; vertical-align: top; }
.reference__table th { color: var(--text-darker); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }
.reference__table td:first-child { color: var(--text-main); white-space: nowrap; }
/* Example values (ids, mentions, urls) wrap and break rather than forcing the table wider than its
   container, so reference tables fit at every width. Short examples still sit on one line. */
.reference__example { color: var(--teal); white-space: normal; overflow-wrap: anywhere; }
.reference__table tr + tr td { border-top: 1px solid var(--shadow-d); }
/* Prose tables (the legal pages) hold sentences, not short tokens, so their first column wraps rather
   than running off as one endless line. The nowrap default above is meant for reference tokens. */
.reference__table--prose td:first-child { white-space: normal; }
/* Stacked layout: align-items must return to stretch, otherwise the desktop flex-start (which top-aligns
   the sticky nav beside the content) leaves the stacked content shrink-wrapped to its widest table and
   overflowing the viewport, instead of filling the width. */
@media (max-width: 800px) { .reference { flex-direction: column; align-items: stretch; } .reference__nav { position: static; flex-basis: auto; width: 100%; } }
/* When the content column is too narrow for a legible table (phones, but also a desktop with the
   sidebar and section nav both open), reference and legal tables read as stacked cards: one card per
   row, one labelled line per column, the same treatment the audit tables use. Driven by the content
   width via a container query, so it is right whether the sidebar is expanded or collapsed. */
@container (max-width: 600px) {
    .reference__table { display: block; overflow-x: visible; }
    .reference__table thead { display: none; }
    .reference__table tbody, .reference__table tr, .reference__table td { display: block; width: 100%; }
    .reference__table tr { padding: 0.85rem 0; }
    .reference__table tr + tr { border-top: 1px solid var(--shadow-d); }
    .reference__table td { padding: 0.1rem 0; text-align: left; white-space: normal; border: 0; }
    /* Beat the base td:first-child nowrap (higher specificity than a bare td) so long first-column
       prose, e.g. the legal pages' data column, wraps inside its card instead of overflowing. */
    .reference__table td:first-child { white-space: normal; }
    .reference__table td.mono, .reference__table .reference__example { overflow-wrap: anywhere; }
    .reference__table td[data-label]::before {
        content: attr(data-label) ": "; color: var(--text-darker); font-weight: 700;
        font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
    }
}
/* Guides read as a numbered walkthrough, not a tight bullet list. Engraved teal number badges on a
   connecting rail carry the sequence, the goal leads in, and a recessed panel with a green label
   closes the journey. Every step gets room to breathe. */
/* Fill the docs body column like the other section types (its own 56rem cap applies), so the guide text and
   the pager share the one column width instead of the guide being a narrower island. */
.guide { max-width: 100%; }
.guide__block { margin-top: 2rem; }
.guide__block:first-child { margin-top: 0.5rem; }
/* The guide's section headings (Goal, Steps, What you end up with) are now real anchored headings, so they
   read and link like every other reference section. The outcome heading keeps its green accent, since the
   outcome block is the payoff and was green before. */
.guide .section-title { margin-top: 0; }
.guide__outcome .section-title { color: var(--green); }
.guide__goal-text { color: var(--text-main); font-size: 18px; line-height: 1.6; margin: 0; }
.guide__steps { list-style: none; counter-reset: guide-step; padding: 0; margin: 0; }
.guide__steps > li { position: relative; counter-increment: guide-step; padding: 0 0 1.9rem 3.5rem; }
.guide__steps > li:last-child { padding-bottom: 0; }
.guide__steps > li::before {
    content: counter(guide-step); position: absolute; left: 0; top: 0;
    width: 2.4rem; height: 2.4rem; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-mono); font-size: 0.9rem; font-weight: 700; color: var(--accent);
    background: var(--base); box-shadow: var(--raised-sm);
}
.guide__steps > li::after {
    content: ''; position: absolute; left: 1.2rem; top: 2.4rem; bottom: 0;
    width: 2px; margin-left: -1px; background: var(--shadow-d);
}
.guide__steps > li:last-child::after { display: none; }
.guide__step-text { display: block; padding-top: 0.5rem; color: var(--text-muted); font-size: 15px; line-height: 1.7; }
.guide__outcome { margin-top: 2rem; padding: 1.3rem 1.5rem; border-radius: 16px; background: var(--base); box-shadow: var(--pressed-sm); }
.guide__outcome-text { color: var(--text-muted); font-size: 15px; line-height: 1.65; margin: 0; }
/* Grouped variable tables (069): a subheading above each short table so the long list reads in sections. */
.reference__group { font-size: 1rem; margin: 1.75rem 0 0.5rem; }
.reference__group:first-of-type { margin-top: 0.5rem; }
/* Documentation lays out as two columns on desktop: the nav rail on the left, the body beside it, the pair centered.
   The nav rail stays a fixed side rail and the body shrinks (capped at the reading width) as the column narrows, so
   the nav does not jump to the top. Only when the body would be too cramped to read does it stack (the container
   query below), which also covers phones. Driven by the docs content box, so it accounts for the sidebar width. */
.view-body:has(.docs-page) { max-width: none; }
.docs-page { container-type: inline-size; }
.reference { display: grid; grid-template-columns: 13rem minmax(0, 56rem); column-gap: 2.5rem; justify-content: center; align-items: start; margin-top: 1.5rem; }
.docs-main { display: flex; flex-direction: column; min-width: 0; }
.docs-main > .reference__content { margin-top: 1.25rem; }
/* In the docs, the description, permissions and page intro fill the body column rather than the app-wide 36rem
   subtitle measure, so all the body text shares the one column width. (The card intros stay bounded by the card.) */
.docs-main > .view-sub, .reference__content .view-sub { max-width: 100%; }
/* Not-found has no nav; center its text in the same reading width. */
.docs-page > .eyebrow, .docs-page > .view-title, .docs-page > .view-sub, .docs-page > p { max-width: 56rem; margin-inline: auto; }
.docs-spacer { display: none; }
/* Documentation rework (069): the Reference landing chapter index and the Next/Previous pager. */
.docs-index { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1.25rem; margin-top: 1.5rem; }
.docs-index__chapter { padding: 1.5rem 1.6rem; }
.docs-index__chapter .section-title a { color: var(--text-main); text-decoration: none; }
.docs-index__chapter .section-title a:hover, .docs-index__chapter .section-title a:focus-visible { color: var(--accent); }
.docs-index__sections { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.docs-index__sections a { color: var(--text-muted); text-decoration: none; font-size: 14px; transition: color 0.15s ease; }
.docs-index__sections a:hover, .docs-index__sections a:focus-visible { color: var(--accent); }
/* The pager's two buttons are equal, each sized to the longer of the two labels, sit close together, and are centered
   under the body. align-self shrink-wraps the pager within the body column, and the equal auto columns make both
   buttons match the wider one. On a narrow column they shrink and their titles ellipsize rather than wrapping. */
.docs-pager { align-self: center; max-width: 100%; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.9rem; margin-top: 3rem; }
/* A page with only one neighbour (the first or last section) shows a single button sized to its own content. */
.docs-pager:has(.btn:only-child) { grid-template-columns: minmax(0, max-content); }
.docs-pager .btn { min-width: 0; overflow: hidden; margin-top: 0; display: inline-flex; align-items: center; gap: 0.6rem; }
.docs-pager__prev { justify-content: flex-start; text-align: left; }
.docs-pager__next { justify-content: flex-end; text-align: right; }
/* Both lines of a button align to the same side: the Previous button reads left, the Next button reads right. */
.docs-pager__label { display: inline-flex; flex-direction: column; min-width: 0; overflow: hidden; }
.docs-pager__prev .docs-pager__label { align-items: flex-start; text-align: left; }
.docs-pager__next .docs-pager__label { align-items: flex-end; text-align: right; }
.docs-pager__dir { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-darker); max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.docs-pager__title { max-width: 100%; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The documentation navigation tree (069 inc. 2): collapsible chapters, location highlight, filter. A sticky side
   rail on desktop. */
.docs-nav { position: sticky; top: 1.5rem; align-self: start; }
/* When the body beside the rail would be too cramped to read (a narrow window with the sidebar expanded, or a phone),
   stack the nav above the body instead. Driven by the docs content box, so it accounts for the sidebar width. */
@container (max-width: 38rem) {
    .reference { grid-template-columns: 1fr; }
    .docs-nav { position: static; margin-bottom: 1.5rem; }
}
.docs-nav__filter { width: 100%; margin-bottom: 1rem; }
.docs-nav__empty { color: var(--text-darker); font-size: 13px; padding: 0.25rem 0.5rem; margin: 0 0 0.5rem; }
.docs-nav__tree { display: flex; flex-direction: column; gap: 0.15rem; }
.docs-nav__chapter-btn { display: flex; align-items: center; gap: 0.5rem; width: 100%; background: none; border: none; padding: 0.4rem 0.5rem; color: var(--text-main); font: inherit; font-weight: 600; cursor: pointer; text-align: left; border-radius: 8px; }
.docs-nav__chapter-btn:hover, .docs-nav__chapter-btn:focus-visible { color: var(--accent); }
.docs-nav__caret { width: 13px; height: 13px; transition: transform 0.18s ease; flex-shrink: 0; }
.docs-nav__chapter.is-open > .docs-nav__chapter-btn .docs-nav__caret { transform: rotate(90deg); }
.docs-nav__sections { list-style: none; margin: 0.1rem 0 0.3rem; padding: 0 0 0 1.5rem; display: none; flex-direction: column; gap: 0.1rem; }
.docs-nav__chapter.is-open > .docs-nav__sections { display: flex; }
.docs-nav__link { display: block; padding: 0.3rem 0.55rem; color: var(--text-muted); text-decoration: none; font-size: 13px; border-radius: 8px; transition: color 0.15s ease; }
.docs-nav__link:hover, .docs-nav__link:focus-visible { color: var(--accent); }
.docs-nav__link.is-current { color: var(--accent); font-weight: 600; background: var(--base); box-shadow: var(--pressed-sm); }
[data-docs-item][hidden], [data-docs-chapter][hidden] { display: none !important; }
/* The grid-to-stacked switch is handled by the @container query above (on the docs content box), not a viewport
   media query, so it accounts for the sidebar width. */
/* Breadcrumb root: a home icon that returns to the main screen, replacing the "Lowkey" wordmark.
   Slightly larger than the chevron so it reads as a control and gives a real tap target. */
.crumb-home { display: inline-flex; align-items: center; color: var(--text-darker); text-decoration: none; transition: color 0.15s ease; }
.crumb-home:hover, .crumb-home:focus-visible { color: var(--text-main); }
.crumb-home .ic { width: 16px; height: 16px; }
/* Phones: let the breadcrumb wrap at its separators instead of squeezing a long mono page title into
   the narrow leftover space (which stacked it into several short lines). The trail stays on one line
   and the current title drops to its own full-width line, kept on a single line of its own. */
@media (max-width: 767.98px) {
    .eyebrow { flex-wrap: wrap; row-gap: 0.3rem; }
    .eyebrow .crumb-current { white-space: nowrap; }
    /* The pager's narrow-width tightening is handled by the @container query above (equal gaps), not here, so it
       stays correct whether the sidebar is open or closed. */
}
.rule__title { font-size: 18px; }
/* Button shadow guard: .btn reserves top clearance so its raised shadow never falls behind the element
   above it (the sign-in/sign-out fix, generalized). Reset it where buttons sit in a horizontal row
   beside other content, not below it, so they stay aligned with their neighbours. */
.rule__controls .btn,
.audit-foot .btn,
.log-filter .btn { margin-top: 0; }
.rule__state { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; padding: 2px 8px; border-radius: 6px; margin-left: 8px; vertical-align: middle; font-family: var(--mono, monospace); }
.rule__state--enabled { color: var(--teal); background: color-mix(in srgb, var(--teal) 14%, transparent); }
.rule__state--off { color: var(--text-darker); background: var(--shadow-d); }
.rule__state--draft { color: #d9a441; background: color-mix(in srgb, #d9a441 14%, transparent); }

/* The searchable entity picker (030). */
.picker { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
.picker__bar { display: flex; align-items: center; gap: 0.5rem; }
.picker__filter { flex: 1; }
.picker__refresh { margin-top: 0; padding: 0.6rem 1.1rem; font-size: 13px; flex-shrink: 0; }
.picker__textarea { width: 100%; min-height: 4.5rem; resize: vertical; line-height: 1.5; }
.picker__textarea::-webkit-resizer { background: transparent; }
.picker__note { font-size: 12px; color: var(--text-muted); }
.picker__actions { display: flex; align-items: center; gap: 1rem; }
.picker__link { background: none; border: none; padding: 0; margin: 0; color: var(--teal); font-family: inherit; font-size: 12px; cursor: pointer; }
.picker__link:hover { text-decoration: underline; }
/* A fixed height so filtering the list never resizes the card. */
.picker__list { height: 12rem; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; padding: 0.5rem; border-radius: 10px; box-shadow: var(--pressed); }
.picker__opt { display: flex; align-items: center; gap: 0.6rem; padding: 5px 8px; border-radius: 8px; cursor: pointer; font-size: 14px; color: var(--text-darker); }
/* A picked row reads slightly brighter than an unpicked one. */
.picker__opt.is-picked { color: var(--text-main); }
.picker__empty { font-size: 13px; color: var(--text-darker); padding: 0.5rem; }
/* Checkboxes in the Aegis language: a raised key that presses IN (inset) and shows a teal check when
   ticked, so a picked item is pushed in and an unpicked one stands proud (the depth law). */
.picker__opt input[type="checkbox"] { appearance: none; -webkit-appearance: none; width: 20px; height: 20px; border-radius: 6px; background: var(--base); box-shadow: var(--raised-sm); cursor: pointer; position: relative; flex-shrink: 0; margin: 0; transition: box-shadow 0.15s ease; }
.picker__opt input[type="checkbox"]:checked { box-shadow: var(--pressed-sm); }
.picker__opt input[type="checkbox"]:checked::after { content: ""; position: absolute; left: 7px; top: 3px; width: 5px; height: 10px; border: solid var(--teal); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.rule__controls { display: flex; align-items: center; gap: 1.5rem; align-self: flex-end; }
.rule__channels { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 2px solid var(--shadow-d); }

.meta-line { display: flex; flex-wrap: wrap; align-items: center; gap: 0.55rem; font-size: 14px; color: var(--text-muted); margin-top: 0.5rem; }
.meta-line .sep { color: var(--shadow-l); user-select: none; }
.meta-line .k { color: var(--text-darker); }
.meta-line .danger { color: var(--red); }
.meta-line .warn { color: var(--yellow); }
.add-inline { background: none; border: none; font-family: inherit; font-size: 14px; color: var(--accent); font-weight: 700; cursor: pointer; display: inline-flex; align-items: center; gap: 0.4rem; padding: 0; }
.add-inline:hover { text-decoration: underline; }

.channels { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 1rem; }
.channel { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; border-radius: 12px; background: var(--flat); font-size: 14px; font-weight: 400; }
.channel .channel-hash { color: var(--text-darker); }
.channel .channel-remove { margin-left: 0.5rem; color: var(--text-darker); cursor: pointer; transition: color 0.2s ease; display: inline-flex; }
.channel .channel-remove:hover { color: var(--red); }
.channel--add { background: var(--base); box-shadow: var(--raised-sm); color: var(--text-muted); border: none; cursor: pointer; font-family: inherit; transition: color 0.2s ease, box-shadow 0.2s ease; }
.channel--add:hover { color: var(--text-main); box-shadow: var(--raised); }
.channel--add:active { color: var(--text-main); box-shadow: var(--pressed-key); }

.add-rule { margin-top: 1rem; width: 100%; padding: 1.25rem; border: none; border-radius: 12px; background: var(--base); box-shadow: var(--raised-sm); color: var(--text-muted); font-family: inherit; font-size: 15px; font-weight: 400; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 0.75rem; transition: color 0.2s ease, box-shadow 0.2s ease; }
.add-rule:hover { color: var(--text-main); box-shadow: var(--raised); }
.add-rule:active { color: var(--text-main); box-shadow: var(--pressed-key); }

/* ---------- settings panel ---------- */
.panel { padding: 1.5rem; }
.panel__title { font-size: 18px; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 2px solid var(--shadow-d); }
.fields { display: flex; flex-direction: column; gap: 2rem; }
.field-label { margin-bottom: 0.75rem; }
.setting-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.setting-row__title { font-size: 16px; font-weight: 700; }
.setting-row__desc { font-size: 14px; color: var(--text-darker); margin-top: 0.25rem; }

/* ---------- audit log ---------- */
/* The wrap is a query container so the table below responds to its own width (which already reflects the sidebar
   and whether it sits in a narrow settings card), not the raw viewport. */
.audit-wrap { overflow-x: auto; container-type: inline-size; }
.audit-table { width: 100%; min-width: 700px; border-collapse: collapse; text-align: left; }
.audit-table th { padding: 1.25rem 2rem; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-darker); border-bottom: 2px solid var(--shadow-d); }
.audit-table th.is-right { text-align: right; }
.audit-table td { padding: 1.25rem 2rem; vertical-align: middle; }
.audit-table tbody tr { border-top: 2px solid var(--shadow-d); transition: background 0.2s ease; }
.audit-table tbody tr:hover { background: rgba(255, 255, 255, 0.02); }
/* When the table would be wider than its container (a narrow window, the sidebar expanded, or a narrow settings
   card), it stacks into labelled rows instead of scrolling sideways, and every cell wraps its content inside the row
   rather than forcing width. Placed after the base rules so min-width: 0 wins, and container-driven so it is right in
   both the narrow-desktop and phone cases. */
@container (max-width: 700px) {
    .audit-wrap { overflow-x: visible; }
    .audit-table { min-width: 0; }
    .audit-table thead { display: none; }
    .audit-table tbody, .audit-table tr, .audit-table td { display: block; width: 100%; }
    /* One clean divider between card rows: drop the base 2px top border (which otherwise reads as a stray bar at the
       top where the header used to be) and give each row a real inset so the content is not flush to the card edge. */
    .audit-table tbody tr { border-top: 0; }
    .audit-table tr { padding: 0.85rem 1.5rem; border-bottom: 1px solid var(--shadow-d); }
    .audit-table tr:last-child { border-bottom: 0; }
    .audit-table td { padding: 0.15rem 0; text-align: left; overflow-wrap: anywhere; }
    .audit-table .cell-right { text-align: left; }
    .audit-table td[data-label]::before { content: attr(data-label) ": "; color: var(--text-darker); font-weight: 700; font-size: 12px; }
}
.event { display: flex; align-items: center; gap: 1.25rem; }
.event__icon { width: 48px; height: 48px; border-radius: 12px; font-size: 18px; color: var(--text-muted); flex-shrink: 0; }
.event__title { font-weight: 700; font-size: 16px; }
.event__desc { font-size: 14px; color: var(--text-muted); margin-top: 0.25rem; display: flex; align-items: center; gap: 0.5rem; }
/* A ban/softban that also cleared a window of messages (061), shown under the reason. */
.event__meta { font-size: 12px; color: var(--text-darker); margin-top: 0.3rem; display: flex; align-items: center; gap: 0.4rem; }
.event__meta .ic { color: var(--text-darker); }
.cell-strong { color: var(--text-main); font-weight: 700; font-size: 16px; }
.cell-muted { color: var(--text-muted); font-weight: 400; font-size: 16px; }
.cell-right { text-align: right; }
.mod-link { color: var(--accent); font-weight: 700; font-size: 16px; }
.id-pill { font-family: var(--font-mono); font-size: 12px; color: var(--text-darker); background: var(--flat); padding: 2px 8px; border-radius: 6px; margin-left: 0.5rem; }
.channel-pill { color: var(--accent); font-weight: 700; background: var(--flat); padding: 2px 8px; border-radius: 6px; }
.audit-foot { padding: 1.5rem 2rem; border-top: 2px solid var(--shadow-d); display: flex; justify-content: center; background: rgba(0, 0, 0, 0.1); border-radius: 0 0 20px 20px; }

/* ---------- reaction roles ---------- */
.rr-grid { display: grid; grid-template-columns: 1fr; gap: 2rem; }
.rr-card { padding: 2rem; display: flex; flex-direction: column; }
.rr-card__head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.5rem; }
.rr-card__title { font-size: 20px; margin-bottom: 0.5rem; }
.rr-meta { display: flex; align-items: center; gap: 0.75rem; font-size: 14px; color: var(--text-muted); font-weight: 400; }
.rr-channel { display: flex; align-items: center; gap: 0.5rem; background: var(--flat); padding: 0.4rem 0.75rem; border-radius: 8px; }
.rr-channel .ic { color: var(--text-darker); }
.rr-jump { color: var(--accent); cursor: pointer; display: inline-flex; align-items: center; gap: 0.35rem; }
.rr-card__actions { display: flex; gap: 1rem; }
/* flat on the card it already sits on: no panel, no grey fill, no inset */
.rr-preview { margin-bottom: 1.75rem; }
.rr-preview__row { display: flex; gap: 1.25rem; }
.rr-avatar { width: 48px; height: 48px; border-radius: 50%; background: var(--accent-deep); display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; flex-shrink: 0; }
.rr-byline { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }
.rr-byline__name { color: var(--text-main); font-weight: 700; font-size: 16px; }
.bot-tag { display: inline-flex; align-items: center; gap: 0.25rem; background: var(--accent-deep); color: #fff; font-size: 10px; font-weight: 700; text-transform: uppercase; padding: 2px 8px; border-radius: 4px; }
.rr-preview__body { color: var(--text-muted); font-size: 16px; }
.rr-list { margin-top: auto; padding-top: 1rem; display: flex; flex-direction: column; gap: 1rem; }
.rr-row { display: flex; align-items: center; gap: 1.25rem; background: var(--flat); padding: 1rem; border-radius: 12px; font-size: 16px; }
.swatch { width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4); }
.rr-row__assigns { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-darker); }
.rr-row__role { font-weight: 700; }

/* ---------- custom commands ---------- */
.cmd-grid { display: grid; grid-template-columns: 1fr; gap: 2rem; }
.cmd { padding: 1.5rem; cursor: pointer; }
.cmd__head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.5rem; }
.cmd__tag { background: var(--flat); color: var(--accent); padding: 0.5rem 1rem; border-radius: 8px; font-family: var(--font-mono); font-size: 16px; font-weight: 700; }
.cmd__title { font-size: 20px; margin-bottom: 0.75rem; }
.cmd__desc { font-size: 14px; color: var(--text-muted); display: flex; align-items: center; gap: 0.75rem; }
.cmd__desc .ic { color: var(--text-darker); }

/* ---------- placeholder ---------- */
.placeholder-card { padding: 2.5rem; text-align: center; max-width: 32rem; width: 100%; margin: 0 auto; }
.placeholder-icon { width: 96px; height: 96px; border-radius: 50%; font-size: 36px; color: var(--text-darker); margin: 0 auto 2rem; }
.placeholder-card h2 { font-size: 24px; margin-bottom: 1rem; }
.placeholder-card p { color: var(--text-muted); font-size: 16px; margin-bottom: 2.5rem; line-height: 1.6; }

/* ---------- toast stack ---------- */
.toast-stack { position: fixed; bottom: 24px; right: 24px; display: flex; flex-direction: column; gap: 12px; z-index: 100; }

/* ---------- server picker grid ---------- */
/* Cards stay a sensible size and pack more columns as the screen widens, centered so wide screens do not
   stretch a few huge cards across all that empty space. min(100%, ...) keeps one column from overflowing a phone. */
/* Left-anchored (not centered) so the cards do not shift horizontally as the column count changes when the
   sidebar collapses or expands, which read as an unstable swap. Extra top space separates them from the search. */
.server-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 300px)); gap: 1.5rem; margin-top: 2rem; justify-content: center; }
.server-card { padding: 1.75rem; display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; }
.server-card.is-unavailable { opacity: 0.6; }
.server-card__icon { width: 56px; height: 56px; border-radius: 50%; background: var(--accent-deep); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 20px; overflow: hidden; box-shadow: var(--raised-sm); }
/* The server's real Discord icon fills the card slot (037), the monogram stays the fallback. */
.server-card__icon-img { width: 100%; height: 100%; object-fit: cover; }
.server-card__name { font-size: 18px; font-weight: 700; }
.server-card__note { font-size: 14px; color: var(--text-muted); display: flex; align-items: center; gap: 0.5rem; }
/* A server the bot is not in yet reads dimmer (010), with its icon greyed, so it is clearly an invite card. */
.server-card--absent { opacity: 0.72; }
.server-card--absent .server-card__icon { background: var(--base); color: var(--text-darker); box-shadow: var(--raised-sm); }
/* The card action (Manage / Selected / Add to server) is the same size and aligned to the card foot. */
.server-card .btn { width: 100%; justify-content: center; margin-top: auto; }
/* After clicking Add to server, the follow-up tells the user how to return and confirm. */
.invite-followup { font-size: 13px; color: var(--text-muted); margin: 0; }

/* The access gate (041). The page body renders as a dimmed, inert teaser behind an overlay that names the
   missing step. `inert` on the teaser makes the whole subtree non-interactive, the dim is only cosmetic. */
.gate { position: relative; }
.gate-teaser { opacity: 0.45; filter: grayscale(0.35); user-select: none; }
.gate-overlay { position: absolute; inset: 0; display: flex; align-items: flex-start; justify-content: center; padding-top: 2.5rem; }
.gate-overlay__panel { padding: 2.5rem; text-align: center; max-width: 30rem; display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.gate-overlay__panel .placeholder-icon { margin-bottom: 1rem; }
.gate-overlay__msg { color: var(--text-muted); margin: 0 0 1.5rem; }
/* A nav entry whose prerequisite is unmet is locked (041), dimmed and not clickable, with a reason on hover. */
.nav-item.is-locked { opacity: 0.5; cursor: not-allowed; }
.nav-item.is-locked:hover { box-shadow: none; color: inherit; }
.nav-item__lock { margin-left: auto; width: 14px; height: 14px; opacity: 0.8; }

/* The signed-out landing (041): a short info screen for the bot with the sign-in call to action. */
.landing { max-width: 40rem; margin-top: 1.5rem; }
/* These two are paragraphs, so they must out-specify the `.aegis p { margin: 0 }` reset (0,1,1). A bare
   `.landing__lead` (0,1,0) loses to it and silently renders with no margin at all, which is what left the
   sign-in note jammed against the buttons. The `.aegis` prefix takes them to (0,2,0) so their spacing lands. */
.aegis .landing__lead { font-size: 18px; color: var(--text-muted); margin: 0 0 2rem; line-height: 1.6; }
.landing__highlights { list-style: none; padding: 0; margin: 0 0 2.5rem; display: flex; flex-direction: column; gap: 1rem; }
.landing__highlight { display: flex; align-items: center; gap: 0.85rem; }
.landing__highlight .ic { width: 22px; height: 22px; color: var(--accent); flex-shrink: 0; }
.landing__cta { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
.aegis .landing__note { font-size: 14px; color: var(--text-darker); line-height: 1.6; margin: 2.5rem 0 0; max-width: 26rem; }
/* The server name filter and its no-results line. */
.server-search { width: 100%; max-width: 28rem; margin-top: 1.25rem; padding: 0.75rem 1rem; border: none; border-radius: 14px; background: var(--base); box-shadow: var(--pressed); color: var(--text-main); font-size: 15px; }
.server-search:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.server-noresults { margin-top: 1.5rem; color: var(--text-muted); }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (min-width: 640px) {
    .stats { grid-template-columns: repeat(2, 1fr); }
    .rule__head { flex-direction: row; align-items: flex-start; justify-content: space-between; }
    .rule__controls { align-self: flex-start; }
    .unsaved { flex-direction: row; align-items: center; justify-content: space-between; }
}
@media (min-width: 768px) {
    /* The current width of the in-flow icon sidebar, so viewport-centered content (the docs) can subtract it
       and stay dead-centre on screen regardless of whether the bar is collapsed or expanded. */
    .app { flex-direction: row; --sidebar-w: 88px; }
    .app.nav-expanded { --sidebar-w: 240px; }
    /* Desktop only: the icon<->labelled width slides, gated so page load/navigation does not animate it. */
    .app.nav-ready .sidebar { transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1); }
    .mobile-bar { display: none; }
    .view-header { padding: 2.5rem 3rem 1.5rem; }
    .view-body { padding: 1rem 3rem 3rem; }
    .view-title { font-size: 36px; }
    .view-header__row { flex-direction: row; align-items: center; justify-content: space-between; }
    .rule, .panel, .rr-card, .cmd, .placeholder-card { padding: 2rem; }
}
@media (min-width: 1024px) {
    .stats { grid-template-columns: repeat(4, 1fr); }
    .rules { grid-template-columns: repeat(3, 1fr); gap: 2.5rem; }
    .rules-col { grid-column: span 2; }
    .cmd-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1280px) {
    .rr-grid { grid-template-columns: repeat(2, 1fr); }
    .cmd-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- portal glue (Blazor-isms over the Aegis kit) ---------- */
/* Blazor NavLink applies "active"; alias it to the Aegis selected-nav look */
.nav-item { text-decoration: none; }
.nav-item.active { box-shadow: var(--pressed-sm); color: var(--text-main); }
.nav-item.active .nav-icon { color: var(--accent); }
/* the main content column scrolls within the shell, the rail and nav stay fixed on desktop */
.app { min-height: 100vh; }
@media (min-width: 768px) {
    .app { height: 100vh; overflow: hidden; }
    .main { min-height: 0; overflow-y: auto; }
}
a { color: inherit; }
/* anchors styled as buttons must not underline (the kit's buttons were <button>) */
.btn, .add-row { text-decoration: none; }
