privateboard 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +229 -51
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/agent-profile.js +0 -2
- package/public/app.js +765 -61
- package/public/home.html +609 -1
- package/public/i18n.js +2 -0
- package/public/index.html +726 -95
- package/public/themes.css +38 -0
- package/public/typing-sfx.js +58 -1
- package/public/user-settings.js +3 -1
package/public/index.html
CHANGED
|
@@ -1904,51 +1904,312 @@
|
|
|
1904
1904
|
|
|
1905
1905
|
/* ──────────────────────────────────────────────────────────
|
|
1906
1906
|
Search page · cross-room keyword search results.
|
|
1907
|
-
|
|
1908
|
-
|
|
1907
|
+
Two postures driven by `.is-initial` / `.has-results` on
|
|
1908
|
+
`.search-page` (toggled by `app.renderSearchPage` /
|
|
1909
|
+
`app.runSearch`):
|
|
1910
|
+
· is-initial · vertically-centred Google-style hero with
|
|
1911
|
+
a wide search input and a mono caption beneath. Reads
|
|
1912
|
+
as "this IS the page" rather than chrome above an
|
|
1913
|
+
empty list.
|
|
1914
|
+
· has-results · the hero collapses, the input shrinks to
|
|
1915
|
+
a compact head row paired with the result-count meta,
|
|
1916
|
+
and the list below uses Google-style 3-line rows
|
|
1917
|
+
(mono source breadcrumb · sans link title · sans
|
|
1918
|
+
snippet body).
|
|
1919
|
+
The same input element serves both states so the focus /
|
|
1920
|
+
value / cursor position survive the swap. */
|
|
1909
1921
|
.search-page {
|
|
1910
|
-
flex: 1
|
|
1911
|
-
|
|
1912
|
-
|
|
1922
|
+
/* No `flex: 1` here · search-page must grow naturally with
|
|
1923
|
+
its content (especially long result lists) so the sticky
|
|
1924
|
+
`.search-card`'s containing block extends through all the
|
|
1925
|
+
rows. With `flex: 1`, the search-page was clamped to one
|
|
1926
|
+
viewport tall and the sticky card un-stuck after scrolling
|
|
1927
|
+
past that — exactly the "sticky disappears after a few
|
|
1928
|
+
scrolls" bug. `min-height: 100%` is still the floor so the
|
|
1929
|
+
is-initial hero can vertical-centre against a viewport-
|
|
1930
|
+
sized stage. */
|
|
1913
1931
|
width: 100%;
|
|
1932
|
+
max-width: 920px;
|
|
1914
1933
|
margin: 0 auto;
|
|
1915
|
-
|
|
1916
|
-
.search-page-head {
|
|
1934
|
+
padding: 32px 32px 40px;
|
|
1917
1935
|
display: flex;
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1936
|
+
flex-direction: column;
|
|
1937
|
+
min-height: 100%;
|
|
1938
|
+
box-sizing: border-box;
|
|
1939
|
+
/* NO `position: relative` here · the deco layers below
|
|
1940
|
+
anchor to `.main-view[data-main-view="search"]` instead
|
|
1941
|
+
(which IS position:relative) so they fill main-view's
|
|
1942
|
+
width — the visible room content area to the right of
|
|
1943
|
+
the sidebar — rather than the .search-page's own 920px
|
|
1944
|
+
column. Earlier the deco was anchored here and used a
|
|
1945
|
+
`width: 100vw` trick to escape, but `vw` units include
|
|
1946
|
+
the sidebar's slice of the viewport, so the deco's left
|
|
1947
|
+
edge ended up underneath the sidebar (and the corner
|
|
1948
|
+
brackets sat where the user couldn't see them). */
|
|
1949
|
+
}
|
|
1950
|
+
/* ─── Initial / empty state · 8-bit ambient deco + wide card ───
|
|
1951
|
+
The page surface gets a static 8-bit "constellation" overlay
|
|
1952
|
+
at the top — scattered pixel dots + a few lime accents —
|
|
1953
|
+
drawn with `shape-rendering: crispEdges` to match the
|
|
1954
|
+
round-table stage / chair-sprite vocabulary. Hero is text-
|
|
1955
|
+
only (longer wordmark, subline) since the prior pixel mark
|
|
1956
|
+
read too noisy. The card itself is 760px and `width: 100%`
|
|
1957
|
+
so it actually fills the centred column instead of shrinking
|
|
1958
|
+
to the input's intrinsic width. */
|
|
1959
|
+
.search-page.is-initial {
|
|
1960
|
+
justify-content: center;
|
|
1961
|
+
/* Nudge slightly above true-centre so the hero feels
|
|
1962
|
+
visually weighted. */
|
|
1963
|
+
padding-bottom: 12vh;
|
|
1964
|
+
padding-top: 0;
|
|
1965
|
+
}
|
|
1966
|
+
/* Has-results · inner-scroll layout. The PAGE itself is sized
|
|
1967
|
+
to exactly main-view height (so nothing scrolls at this
|
|
1968
|
+
level), and the results list inside gets its own scroll
|
|
1969
|
+
via `flex: 1 + overflow-y: auto`. This puts the head row
|
|
1970
|
+
in a non-scrolling top slot — no `position: sticky`
|
|
1971
|
+
gymnastics, no containing-block-too-short failure mode.
|
|
1972
|
+
The previous sticky approach disappeared as soon as the
|
|
1973
|
+
user scrolled past one screen because the sticky's
|
|
1974
|
+
containing block (the page) ran out; this design has no
|
|
1975
|
+
such ceiling. */
|
|
1976
|
+
.search-page.has-results {
|
|
1977
|
+
height: 100%;
|
|
1978
|
+
min-height: 0;
|
|
1979
|
+
padding-top: 32px;
|
|
1980
|
+
padding-bottom: 0;
|
|
1981
|
+
flex-shrink: 0;
|
|
1982
|
+
}
|
|
1983
|
+
/* 8-bit ambient deco · absolute-positioned overlay at the top
|
|
1984
|
+
of the page. Pure decoration · pointer-events: none, aria-
|
|
1985
|
+
hidden, fades out the lower edge with a CSS mask so it
|
|
1986
|
+
doesn't compete with the hero / card below.
|
|
1987
|
+
|
|
1988
|
+
Width · `left: 0; right: 0` resolves against the nearest
|
|
1989
|
+
positioned ancestor, which is `.main-view[data-main-view=
|
|
1990
|
+
"search"]` (it sits to the right of the sidebar). The deco
|
|
1991
|
+
therefore spans the FULL main-view width, not the viewport
|
|
1992
|
+
— so the sidebar never overlaps. .search-page is static
|
|
1993
|
+
(no position:relative) so the deco escapes its 920px cap
|
|
1994
|
+
naturally. */
|
|
1995
|
+
.search-bg-deco {
|
|
1996
|
+
position: absolute;
|
|
1997
|
+
top: 0;
|
|
1998
|
+
left: 0;
|
|
1999
|
+
right: 0;
|
|
2000
|
+
height: 280px;
|
|
2001
|
+
pointer-events: none;
|
|
2002
|
+
z-index: -1;
|
|
2003
|
+
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
2004
|
+
mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
2005
|
+
opacity: 0.85;
|
|
2006
|
+
}
|
|
2007
|
+
.search-bg-deco svg { display: block; width: 100%; height: 100%; }
|
|
2008
|
+
|
|
2009
|
+
/* Positioning anchor for the deco layers · main-view sits
|
|
2010
|
+
to the right of the sidebar so its bounds = the visible
|
|
2011
|
+
"room content" width. `isolation: isolate` scopes the
|
|
2012
|
+
deco's z-index: -1 so it sits behind search-page content
|
|
2013
|
+
without leaking past main-view's background. */
|
|
2014
|
+
.main-view[data-main-view="search"] {
|
|
2015
|
+
position: relative;
|
|
2016
|
+
isolation: isolate;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
.search-page.has-results .search-bg-deco {
|
|
2020
|
+
/* Compact in has-results · keep a slim band of pixel
|
|
2021
|
+
constellation behind the head row so the page still
|
|
2022
|
+
reads as the search page rather than a generic list. */
|
|
2023
|
+
height: 130px;
|
|
2024
|
+
opacity: 0.45;
|
|
2025
|
+
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 50%, transparent 100%);
|
|
2026
|
+
mask-image: linear-gradient(180deg, #000 0%, #000 50%, transparent 100%);
|
|
2027
|
+
transition: opacity 0.18s ease, height 0.22s ease;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
/* Results-only deco · second layer that ONLY shows in
|
|
2031
|
+
has-results. Adds proper 8-bit characters on top of the
|
|
2032
|
+
constellation: CRT scanlines (CSS), pixel antennas at
|
|
2033
|
+
the corners, scattered pixel "plus" sparkles, and a
|
|
2034
|
+
dashed horizon line at the bottom edge. The combination
|
|
2035
|
+
gives the results header genuine "search station"
|
|
2036
|
+
atmosphere instead of a thin star field. */
|
|
2037
|
+
.search-results-deco {
|
|
2038
|
+
position: absolute;
|
|
2039
|
+
top: 0;
|
|
2040
|
+
/* Spans full main-view width · the deco's `position:
|
|
2041
|
+
absolute` resolves to `.main-view[data-main-view=
|
|
2042
|
+
"search"]` (now position:relative) since .search-page
|
|
2043
|
+
is static. No `100vw` trick needed — main-view already
|
|
2044
|
+
sits to the right of the sidebar. */
|
|
2045
|
+
left: 0;
|
|
2046
|
+
right: 0;
|
|
2047
|
+
height: 130px;
|
|
2048
|
+
pointer-events: none;
|
|
2049
|
+
z-index: -1;
|
|
2050
|
+
opacity: 0;
|
|
2051
|
+
transition: opacity 0.22s ease;
|
|
2052
|
+
/* Faint CRT scanlines · 1px lime-tinted line every 5px.
|
|
2053
|
+
Subtle enough not to compete with the input but
|
|
2054
|
+
persistent enough to read as "this surface is alive." */
|
|
2055
|
+
background-image: repeating-linear-gradient(
|
|
2056
|
+
0deg,
|
|
2057
|
+
transparent 0px,
|
|
2058
|
+
transparent 4px,
|
|
2059
|
+
rgba(111, 181, 114, 0.035) 4px,
|
|
2060
|
+
rgba(111, 181, 114, 0.035) 5px
|
|
2061
|
+
);
|
|
2062
|
+
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
|
|
2063
|
+
mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
|
|
2064
|
+
}
|
|
2065
|
+
.search-page.has-results .search-results-deco {
|
|
2066
|
+
opacity: 0.85;
|
|
1924
2067
|
}
|
|
1925
|
-
.search-
|
|
2068
|
+
.search-results-deco svg { display: block; width: 100%; height: 100%; }
|
|
2069
|
+
.search-hero {
|
|
2070
|
+
text-align: center;
|
|
2071
|
+
max-width: 760px;
|
|
2072
|
+
margin: 0 auto 26px;
|
|
2073
|
+
overflow: hidden;
|
|
2074
|
+
transition:
|
|
2075
|
+
opacity 0.22s ease,
|
|
2076
|
+
max-height 0.24s ease,
|
|
2077
|
+
margin 0.24s ease;
|
|
2078
|
+
}
|
|
2079
|
+
.search-page.is-initial .search-hero {
|
|
2080
|
+
opacity: 1;
|
|
2081
|
+
max-height: 220px;
|
|
2082
|
+
}
|
|
2083
|
+
.search-page.has-results .search-hero {
|
|
2084
|
+
opacity: 0;
|
|
2085
|
+
max-height: 0;
|
|
2086
|
+
margin-top: 0;
|
|
2087
|
+
margin-bottom: 0;
|
|
2088
|
+
pointer-events: none;
|
|
2089
|
+
}
|
|
2090
|
+
.search-hero-title {
|
|
1926
2091
|
font-family: var(--font-human);
|
|
1927
|
-
font-size:
|
|
1928
|
-
font-weight:
|
|
1929
|
-
letter-spacing: -0.
|
|
2092
|
+
font-size: 28px;
|
|
2093
|
+
font-weight: 600;
|
|
2094
|
+
letter-spacing: -0.02em;
|
|
1930
2095
|
color: var(--text);
|
|
1931
|
-
|
|
2096
|
+
line-height: 1.05;
|
|
2097
|
+
margin: 0 0 12px 0;
|
|
1932
2098
|
}
|
|
1933
|
-
.search-
|
|
2099
|
+
.search-hero-sub {
|
|
1934
2100
|
font-family: var(--mono);
|
|
1935
|
-
font-size:
|
|
1936
|
-
letter-spacing: 0.
|
|
2101
|
+
font-size: 11px;
|
|
2102
|
+
letter-spacing: 0.18em;
|
|
1937
2103
|
text-transform: uppercase;
|
|
1938
2104
|
color: var(--text-faint);
|
|
1939
|
-
|
|
1940
|
-
margin-bottom: 4px;
|
|
2105
|
+
margin: 0;
|
|
1941
2106
|
}
|
|
2107
|
+
|
|
2108
|
+
/* Search card · the substantial input affordance for the
|
|
2109
|
+
is-initial state. `width: 100%` is load-bearing — without
|
|
2110
|
+
it the card collapses to its content's intrinsic width
|
|
2111
|
+
(the input had no flex anchor in is-initial because the
|
|
2112
|
+
wrap was display:flex with the input as a single child),
|
|
2113
|
+
making the whole hero look narrow. With `width: 100%`
|
|
2114
|
+
plus `max-width: 760px` the card always fills the
|
|
2115
|
+
centred column. In has-results the card chrome strips
|
|
2116
|
+
and the inner input-wrap takes back its own border so
|
|
2117
|
+
the compact head row can flex input + meta side-by-side. */
|
|
2118
|
+
.search-card {
|
|
2119
|
+
display: block;
|
|
2120
|
+
width: 100%;
|
|
2121
|
+
max-width: 760px;
|
|
2122
|
+
margin: 0 auto;
|
|
2123
|
+
box-sizing: border-box;
|
|
2124
|
+
/* Mirror the new-room composer's `.cmp-input-frame` (line
|
|
2125
|
+
~9989) — the frame is black (`var(--bg)`), the input
|
|
2126
|
+
inside is transparent so it inherits the black, and the
|
|
2127
|
+
toolbar at the bottom is `panel-2` (lighter). The two
|
|
2128
|
+
tones plus the hairline divider make it read as one
|
|
2129
|
+
designed input object. `overflow: hidden` is load-bearing
|
|
2130
|
+
so the panel-2 toolbar doesn't bleed past the frame's
|
|
2131
|
+
hairline border. */
|
|
2132
|
+
background: var(--bg);
|
|
2133
|
+
border: 0.5px solid var(--line-strong);
|
|
2134
|
+
overflow: hidden;
|
|
2135
|
+
transition:
|
|
2136
|
+
border-color 0.18s,
|
|
2137
|
+
background 0.18s,
|
|
2138
|
+
max-width 0.22s ease,
|
|
2139
|
+
margin 0.22s ease,
|
|
2140
|
+
padding 0.22s ease;
|
|
2141
|
+
}
|
|
2142
|
+
.search-card:focus-within {
|
|
2143
|
+
border-color: var(--lime);
|
|
2144
|
+
}
|
|
2145
|
+
.search-page.has-results .search-card {
|
|
2146
|
+
display: flex;
|
|
2147
|
+
align-items: center;
|
|
2148
|
+
gap: 14px;
|
|
2149
|
+
max-width: none;
|
|
2150
|
+
/* No `margin-bottom` here · the 18px breathing room below
|
|
2151
|
+
the head row moved INSIDE `.search-results` (via the
|
|
2152
|
+
::before pseudo down at line ~XXX). With it on the card,
|
|
2153
|
+
that 18px lived OUTSIDE the scroll area and permanently
|
|
2154
|
+
stole vertical real estate; the scroll content was
|
|
2155
|
+
truncated 18px earlier than the visible band. Moving the
|
|
2156
|
+
gap into the scroll content means it scrolls away
|
|
2157
|
+
naturally as the user reads down. */
|
|
2158
|
+
margin: 0;
|
|
2159
|
+
padding-top: 12px;
|
|
2160
|
+
padding-bottom: 14px;
|
|
2161
|
+
/* The head row is now in a NON-scrolling top slot of
|
|
2162
|
+
search-page (the page is fixed at main-view height; the
|
|
2163
|
+
scroll lives inside `.search-results`). No `position:
|
|
2164
|
+
sticky` needed — the card stays put because the surface
|
|
2165
|
+
around it doesn't move. */
|
|
2166
|
+
background: var(--panel);
|
|
2167
|
+
border: none;
|
|
2168
|
+
flex-shrink: 0;
|
|
2169
|
+
z-index: 2;
|
|
2170
|
+
/* Stacking context for the full-width ::after pseudo. */
|
|
2171
|
+
position: relative;
|
|
2172
|
+
isolation: isolate;
|
|
2173
|
+
}
|
|
2174
|
+
/* Full-width head band · ::after extends a panel-coloured
|
|
2175
|
+
strip + 0.5px under-line from -100vmax to +100vmax past
|
|
2176
|
+
the card's 920px box, clipped by main-view's overflow.
|
|
2177
|
+
z-index: -1 keeps it behind the card's children (input /
|
|
2178
|
+
chips / meta) inside the card's own stacking context. */
|
|
2179
|
+
.search-page.has-results .search-card::after {
|
|
2180
|
+
content: "";
|
|
2181
|
+
position: absolute;
|
|
2182
|
+
inset: 0 -100vmax;
|
|
2183
|
+
background: var(--panel);
|
|
2184
|
+
border-bottom: 0.5px solid var(--line);
|
|
2185
|
+
z-index: -1;
|
|
2186
|
+
pointer-events: none;
|
|
2187
|
+
}
|
|
2188
|
+
.search-page.has-results .search-card:focus-within {
|
|
2189
|
+
border-color: transparent;
|
|
2190
|
+
border-bottom-color: var(--lime);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
/* Input wrap · borderless inside the card in is-initial,
|
|
2194
|
+
bordered pill in has-results. */
|
|
1942
2195
|
.search-input-wrap {
|
|
1943
2196
|
position: relative;
|
|
1944
2197
|
display: flex;
|
|
1945
2198
|
align-items: center;
|
|
1946
|
-
|
|
2199
|
+
flex: 1;
|
|
2200
|
+
min-width: 0;
|
|
2201
|
+
transition: border-color 0.18s, background 0.18s;
|
|
2202
|
+
}
|
|
2203
|
+
.search-page.is-initial .search-input-wrap {
|
|
2204
|
+
background: transparent;
|
|
2205
|
+
border: none;
|
|
2206
|
+
padding: 6px 4px 0;
|
|
2207
|
+
}
|
|
2208
|
+
.search-page.has-results .search-input-wrap {
|
|
1947
2209
|
background: var(--panel-2);
|
|
1948
2210
|
border: 0.5px solid var(--line);
|
|
1949
|
-
transition: border-color 0.12s, background 0.12s;
|
|
1950
2211
|
}
|
|
1951
|
-
.search-input-wrap:focus-within {
|
|
2212
|
+
.search-page.has-results .search-input-wrap:focus-within {
|
|
1952
2213
|
border-color: var(--lime);
|
|
1953
2214
|
background: var(--panel);
|
|
1954
2215
|
}
|
|
@@ -1956,135 +2217,380 @@
|
|
|
1956
2217
|
display: flex;
|
|
1957
2218
|
align-items: center;
|
|
1958
2219
|
justify-content: center;
|
|
1959
|
-
width: 36px;
|
|
1960
2220
|
color: var(--text-faint);
|
|
1961
2221
|
flex-shrink: 0;
|
|
1962
|
-
|
|
2222
|
+
transition: width 0.22s ease, color 0.12s;
|
|
2223
|
+
}
|
|
2224
|
+
/* Hide the leading magnifier in BOTH states · the hero card
|
|
2225
|
+
is its own affordance, and in has-results the input is
|
|
2226
|
+
unmistakably a search field by context (sort chips +
|
|
2227
|
+
result-count meta beside it). The icon was reading as
|
|
2228
|
+
redundant chrome. */
|
|
2229
|
+
.search-input-icon { display: none; }
|
|
1963
2230
|
.search-input-wrap:focus-within .search-input-icon { color: var(--lime); }
|
|
1964
2231
|
.search-input {
|
|
1965
2232
|
flex: 1;
|
|
1966
2233
|
min-width: 0;
|
|
1967
|
-
padding: 12px 8px;
|
|
1968
2234
|
background: transparent;
|
|
1969
2235
|
border: none;
|
|
1970
2236
|
color: var(--text);
|
|
1971
2237
|
font-family: var(--font-human);
|
|
1972
|
-
font-size: 15px;
|
|
1973
2238
|
letter-spacing: -0.005em;
|
|
1974
2239
|
outline: none;
|
|
2240
|
+
transition: padding 0.22s ease, font-size 0.22s ease;
|
|
2241
|
+
}
|
|
2242
|
+
.search-page.is-initial .search-input {
|
|
2243
|
+
/* Same type spec as `.cmp-input` (14.5px sans, line-height
|
|
2244
|
+
1.55, letter-spacing -0.003em) so the family register
|
|
2245
|
+
matches the new-room composer. SYMMETRIC vertical
|
|
2246
|
+
padding (22 top / 22 bottom) instead of the cmp's
|
|
2247
|
+
20/12 — the cmp is a TEXTAREA where text starts at
|
|
2248
|
+
the top, but our search-input is a SINGLE-LINE input
|
|
2249
|
+
so the text/placeholder must vertical-centre for it
|
|
2250
|
+
to read right. The taller padding also gives the
|
|
2251
|
+
hero card more presence. */
|
|
2252
|
+
padding: 22px 22px;
|
|
2253
|
+
font-size: 14.5px;
|
|
2254
|
+
line-height: 1.55;
|
|
2255
|
+
letter-spacing: -0.003em;
|
|
2256
|
+
}
|
|
2257
|
+
/* Defuse Chrome's autofill bg · when the browser remembers a
|
|
2258
|
+
prior query, the input gets a pale yellow/gray autofill
|
|
2259
|
+
surface that fights our intended black card. The giant
|
|
2260
|
+
inset box-shadow trick paints the autofill area with our
|
|
2261
|
+
own surface color so the input stays visually consistent
|
|
2262
|
+
with the rest of the card whether autofill engaged or not. */
|
|
2263
|
+
.search-input:-webkit-autofill,
|
|
2264
|
+
.search-input:-webkit-autofill:hover,
|
|
2265
|
+
.search-input:-webkit-autofill:focus {
|
|
2266
|
+
-webkit-box-shadow: 0 0 0 1000px var(--bg) inset !important;
|
|
2267
|
+
-webkit-text-fill-color: var(--text) !important;
|
|
2268
|
+
caret-color: var(--text);
|
|
2269
|
+
transition: background-color 5000s ease-in-out 0s;
|
|
2270
|
+
}
|
|
2271
|
+
.search-page.has-results .search-input {
|
|
2272
|
+
padding: 9px 6px;
|
|
2273
|
+
font-size: 13px;
|
|
1975
2274
|
}
|
|
1976
2275
|
.search-input::placeholder {
|
|
1977
2276
|
color: var(--text-faint);
|
|
1978
|
-
font-style:
|
|
2277
|
+
font-style: normal;
|
|
2278
|
+
font-weight: 400;
|
|
2279
|
+
}
|
|
2280
|
+
/* Override the OLDER `.search-input:focus` rule (line ~796)
|
|
2281
|
+
from the sidebar's search-block · that rule sets
|
|
2282
|
+
`background: var(--panel-2)` on focus, which turned our
|
|
2283
|
+
hero input GRAY whenever the user clicked into it. Both
|
|
2284
|
+
features share the class name `.search-input`, so an
|
|
2285
|
+
unscoped focus rule there leaks here. Scoping under
|
|
2286
|
+
`.search-page` raises specificity and pins the input
|
|
2287
|
+
transparent on focus. */
|
|
2288
|
+
.search-page .search-input:focus,
|
|
2289
|
+
.search-page .search-input:focus-visible {
|
|
2290
|
+
background: transparent;
|
|
2291
|
+
border: none;
|
|
2292
|
+
outline: none;
|
|
1979
2293
|
}
|
|
1980
2294
|
.search-input-clear {
|
|
1981
|
-
width: 36px;
|
|
1982
|
-
height: 36px;
|
|
1983
2295
|
background: transparent;
|
|
1984
2296
|
border: none;
|
|
1985
2297
|
color: var(--text-faint);
|
|
1986
|
-
font-size: 13px;
|
|
1987
2298
|
cursor: pointer;
|
|
1988
|
-
transition:
|
|
2299
|
+
transition:
|
|
2300
|
+
color 0.12s,
|
|
2301
|
+
width 0.22s ease,
|
|
2302
|
+
height 0.22s ease;
|
|
1989
2303
|
flex-shrink: 0;
|
|
2304
|
+
display: inline-flex;
|
|
2305
|
+
align-items: center;
|
|
2306
|
+
justify-content: center;
|
|
2307
|
+
}
|
|
2308
|
+
.search-page.is-initial .search-input-clear {
|
|
2309
|
+
width: 36px;
|
|
2310
|
+
height: 36px;
|
|
2311
|
+
font-size: 13px;
|
|
2312
|
+
margin-right: 6px;
|
|
2313
|
+
}
|
|
2314
|
+
.search-page.has-results .search-input-clear {
|
|
2315
|
+
width: 32px;
|
|
2316
|
+
height: 32px;
|
|
2317
|
+
font-size: 12px;
|
|
1990
2318
|
}
|
|
1991
2319
|
.search-input-clear:hover { color: var(--text); }
|
|
1992
|
-
|
|
1993
|
-
|
|
2320
|
+
/* Hide the clear button when the input is empty (set by JS
|
|
2321
|
+
via the .is-empty class on .search-card). Keeps the
|
|
2322
|
+
hero's right edge clean when the user hasn't typed yet. */
|
|
2323
|
+
.search-card.is-empty .search-input-clear { display: none; }
|
|
2324
|
+
|
|
2325
|
+
/* Internal toolbar · footer of the card in is-initial.
|
|
2326
|
+
Mono hint on the left, lime send button on the right.
|
|
2327
|
+
Hidden entirely in has-results (the card is collapsed
|
|
2328
|
+
to a head row by then). */
|
|
2329
|
+
.search-input-toolbar {
|
|
2330
|
+
/* Mirror `.cmp-toolbar` (line ~10028) — same padding /
|
|
2331
|
+
min-height / hairline-divider / panel-2 surface so the
|
|
2332
|
+
two-tone frame reads as one designed input. The panel-2
|
|
2333
|
+
tone is the visual cue that "this is the action row."
|
|
2334
|
+
It's slightly lighter than the input area's black so
|
|
2335
|
+
the eye registers it as a distinct band without a heavy
|
|
2336
|
+
border. */
|
|
2337
|
+
display: flex;
|
|
2338
|
+
align-items: center;
|
|
2339
|
+
justify-content: space-between;
|
|
2340
|
+
padding: 8px 8px 8px 14px;
|
|
2341
|
+
min-height: 48px;
|
|
2342
|
+
border-top: 0.5px solid var(--line);
|
|
2343
|
+
background: var(--panel-2);
|
|
2344
|
+
}
|
|
2345
|
+
.search-page.has-results .search-input-toolbar { display: none; }
|
|
2346
|
+
.search-toolbar-hint {
|
|
2347
|
+
font-family: var(--mono);
|
|
2348
|
+
font-size: 9.5px;
|
|
2349
|
+
letter-spacing: 0.16em;
|
|
2350
|
+
text-transform: uppercase;
|
|
2351
|
+
color: var(--text-faint);
|
|
2352
|
+
}
|
|
2353
|
+
/* Starter chips · static suggestions below the card in
|
|
2354
|
+
is-initial. Click → pre-fill the input + trigger search.
|
|
2355
|
+
Hidden in has-results. */
|
|
2356
|
+
.search-starters {
|
|
2357
|
+
display: flex;
|
|
2358
|
+
align-items: center;
|
|
2359
|
+
justify-content: center;
|
|
2360
|
+
gap: 6px;
|
|
2361
|
+
margin: 20px auto 0;
|
|
2362
|
+
max-width: 760px;
|
|
1994
2363
|
font-family: var(--mono);
|
|
1995
2364
|
font-size: 10px;
|
|
1996
2365
|
letter-spacing: 0.14em;
|
|
1997
2366
|
text-transform: uppercase;
|
|
1998
2367
|
color: var(--text-faint);
|
|
1999
|
-
|
|
2368
|
+
flex-wrap: wrap;
|
|
2000
2369
|
}
|
|
2001
|
-
.search-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2370
|
+
.search-page.has-results .search-starters { display: none; }
|
|
2371
|
+
.search-starters-label { color: var(--text-faint); margin-right: 4px; }
|
|
2372
|
+
.search-starter {
|
|
2373
|
+
background: transparent;
|
|
2374
|
+
border: 0.5px solid var(--line);
|
|
2375
|
+
color: var(--text-soft);
|
|
2376
|
+
font: inherit;
|
|
2377
|
+
letter-spacing: inherit;
|
|
2378
|
+
text-transform: inherit;
|
|
2379
|
+
cursor: pointer;
|
|
2380
|
+
padding: 5px 10px;
|
|
2381
|
+
transition: color 0.12s, border-color 0.12s;
|
|
2005
2382
|
}
|
|
2006
|
-
.search-
|
|
2007
|
-
|
|
2383
|
+
.search-starter:hover {
|
|
2384
|
+
color: var(--lime);
|
|
2385
|
+
border-color: var(--lime);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
/* Result-count meta · sits beside the shrunk input in the
|
|
2389
|
+
has-results head row. Hidden entirely in is-initial. */
|
|
2390
|
+
.search-results-meta {
|
|
2008
2391
|
font-family: var(--mono);
|
|
2009
2392
|
font-size: 10px;
|
|
2010
|
-
letter-spacing: 0.
|
|
2393
|
+
letter-spacing: 0.14em;
|
|
2011
2394
|
text-transform: uppercase;
|
|
2012
|
-
color: var(--
|
|
2013
|
-
|
|
2395
|
+
color: var(--text-faint);
|
|
2396
|
+
white-space: nowrap;
|
|
2397
|
+
flex-shrink: 0;
|
|
2014
2398
|
}
|
|
2015
|
-
.search-
|
|
2016
|
-
|
|
2017
|
-
|
|
2399
|
+
.search-page.is-initial .search-results-meta { display: none; }
|
|
2400
|
+
|
|
2401
|
+
/* Sort chip group · "Newest" / "Oldest" toggle that re-sorts
|
|
2402
|
+
the result list client-side (no re-fetch). Only visible in
|
|
2403
|
+
has-results · the head row's flex layout slots it between
|
|
2404
|
+
the input and the meta count. Active chip uses the lime
|
|
2405
|
+
accent border + caps; inactives are line-faint until hover. */
|
|
2406
|
+
.search-results-sort {
|
|
2407
|
+
display: none;
|
|
2408
|
+
align-items: center;
|
|
2409
|
+
gap: 4px;
|
|
2410
|
+
flex-shrink: 0;
|
|
2411
|
+
font-family: var(--mono);
|
|
2412
|
+
font-size: 9.5px;
|
|
2413
|
+
letter-spacing: 0.14em;
|
|
2414
|
+
text-transform: uppercase;
|
|
2415
|
+
}
|
|
2416
|
+
.search-page.has-results .search-results-sort {
|
|
2417
|
+
display: inline-flex;
|
|
2418
|
+
}
|
|
2419
|
+
.search-results-sort .srs-label {
|
|
2420
|
+
color: var(--text-faint);
|
|
2421
|
+
margin-right: 4px;
|
|
2422
|
+
}
|
|
2423
|
+
.search-results-sort button {
|
|
2424
|
+
background: transparent;
|
|
2425
|
+
border: 0.5px solid var(--line);
|
|
2426
|
+
color: var(--text-faint);
|
|
2427
|
+
font: inherit;
|
|
2428
|
+
letter-spacing: inherit;
|
|
2429
|
+
text-transform: inherit;
|
|
2430
|
+
padding: 5px 9px;
|
|
2431
|
+
cursor: pointer;
|
|
2432
|
+
transition: color 0.12s, border-color 0.12s;
|
|
2433
|
+
}
|
|
2434
|
+
.search-results-sort button:hover {
|
|
2018
2435
|
color: var(--text-soft);
|
|
2019
|
-
|
|
2020
|
-
max-width: 520px;
|
|
2021
|
-
margin: 0 auto;
|
|
2436
|
+
border-color: var(--text-faint);
|
|
2022
2437
|
}
|
|
2438
|
+
.search-results-sort button.active {
|
|
2439
|
+
color: var(--lime);
|
|
2440
|
+
border-color: var(--lime);
|
|
2441
|
+
}
|
|
2442
|
+
.search-results-sort button.active:hover {
|
|
2443
|
+
color: var(--lime);
|
|
2444
|
+
border-color: var(--lime);
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/* Results · only visible in has-results. Top padding gives a
|
|
2448
|
+
small breathing room below the head row's hairline. */
|
|
2449
|
+
.search-results {
|
|
2450
|
+
padding-top: 4px;
|
|
2451
|
+
}
|
|
2452
|
+
.search-page.is-initial .search-results { display: none; }
|
|
2453
|
+
/* Inner scroll container · in has-results, `.search-results`
|
|
2454
|
+
owns the vertical scroll instead of `.main-view`. The
|
|
2455
|
+
search-page itself is sized to exactly main-view height,
|
|
2456
|
+
so the head row (`.search-card`) sits in a non-scrolling
|
|
2457
|
+
top slot and stays there permanently. */
|
|
2458
|
+
.search-page.has-results .search-results {
|
|
2459
|
+
flex: 1;
|
|
2460
|
+
min-height: 0;
|
|
2461
|
+
overflow-y: auto;
|
|
2462
|
+
padding-bottom: 40px;
|
|
2463
|
+
scrollbar-width: thin;
|
|
2464
|
+
}
|
|
2465
|
+
/* Initial breathing-room spacer · 18px tall block that sits
|
|
2466
|
+
INSIDE the scroll content (before the result list). On
|
|
2467
|
+
first render it gives the user the same gap below the
|
|
2468
|
+
head row that a `margin-bottom: 18px` would; once they
|
|
2469
|
+
start scrolling, this spacer scrolls away with the rest
|
|
2470
|
+
of the list (it's part of the scrollable content), so it
|
|
2471
|
+
doesn't permanently consume visible scroll real estate
|
|
2472
|
+
the way an outside-the-scroll margin did. */
|
|
2473
|
+
.search-page.has-results .search-results::before {
|
|
2474
|
+
content: "";
|
|
2475
|
+
display: block;
|
|
2476
|
+
height: 18px;
|
|
2477
|
+
}
|
|
2478
|
+
.search-page.has-results .search-results::-webkit-scrollbar { width: 8px; }
|
|
2479
|
+
.search-page.has-results .search-results::-webkit-scrollbar-thumb { background: transparent; }
|
|
2480
|
+
.search-page.has-results .search-results::-webkit-scrollbar-track { background: transparent; }
|
|
2481
|
+
.search-page.has-results .search-results:hover::-webkit-scrollbar-thumb { background: var(--line-bright); }
|
|
2023
2482
|
|
|
2024
|
-
/*
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
.search-
|
|
2483
|
+
/* Google-style result row · 3 stacked text lines (mono
|
|
2484
|
+
source breadcrumb · sans link title · sans snippet body).
|
|
2485
|
+
No row hover background, no per-row border — only the
|
|
2486
|
+
title line lights up on hover so the row reads calm. */
|
|
2487
|
+
.search-results-list {
|
|
2029
2488
|
list-style: none;
|
|
2030
2489
|
margin: 0;
|
|
2031
2490
|
padding: 0;
|
|
2032
|
-
border-top: 0.5px solid var(--line);
|
|
2033
2491
|
}
|
|
2034
|
-
.search-
|
|
2492
|
+
.search-results-list > li {
|
|
2493
|
+
margin-bottom: 22px;
|
|
2494
|
+
}
|
|
2495
|
+
.search-results-list > li:last-child { margin-bottom: 0; }
|
|
2496
|
+
.sr-row {
|
|
2035
2497
|
display: block;
|
|
2036
|
-
padding: 12px 14px;
|
|
2037
2498
|
text-decoration: none;
|
|
2038
2499
|
color: inherit;
|
|
2039
|
-
border-bottom: 0.5px solid var(--line);
|
|
2040
|
-
transition: background 0.1s;
|
|
2041
2500
|
}
|
|
2042
|
-
.
|
|
2043
|
-
|
|
2501
|
+
.sr-source {
|
|
2502
|
+
display: flex;
|
|
2503
|
+
align-items: baseline;
|
|
2504
|
+
gap: 6px;
|
|
2505
|
+
font-family: var(--mono);
|
|
2506
|
+
font-size: 10px;
|
|
2507
|
+
letter-spacing: 0.12em;
|
|
2508
|
+
text-transform: uppercase;
|
|
2509
|
+
color: var(--text-faint);
|
|
2510
|
+
margin-bottom: 4px;
|
|
2511
|
+
overflow: hidden;
|
|
2512
|
+
white-space: nowrap;
|
|
2513
|
+
text-overflow: ellipsis;
|
|
2514
|
+
}
|
|
2515
|
+
.sr-source-author {
|
|
2516
|
+
color: var(--text-soft);
|
|
2517
|
+
font-weight: 600;
|
|
2518
|
+
}
|
|
2519
|
+
.sr-source-sep {
|
|
2520
|
+
color: var(--text-faint);
|
|
2521
|
+
margin: 0 2px;
|
|
2522
|
+
}
|
|
2523
|
+
.sr-source-time {
|
|
2524
|
+
color: var(--text-faint);
|
|
2525
|
+
}
|
|
2526
|
+
.sr-title {
|
|
2044
2527
|
font-family: var(--font-human);
|
|
2045
|
-
font-size:
|
|
2046
|
-
|
|
2528
|
+
font-size: 17px;
|
|
2529
|
+
font-weight: 500;
|
|
2530
|
+
line-height: 1.3;
|
|
2047
2531
|
color: var(--text);
|
|
2532
|
+
margin: 0 0 4px 0;
|
|
2533
|
+
letter-spacing: -0.005em;
|
|
2534
|
+
transition: color 0.12s;
|
|
2535
|
+
overflow: hidden;
|
|
2536
|
+
text-overflow: ellipsis;
|
|
2537
|
+
white-space: nowrap;
|
|
2538
|
+
}
|
|
2539
|
+
.sr-row:hover .sr-title { color: var(--lime); }
|
|
2540
|
+
.sr-snippet {
|
|
2541
|
+
font-family: var(--font-human);
|
|
2542
|
+
font-size: 13px;
|
|
2543
|
+
line-height: 1.55;
|
|
2544
|
+
color: var(--text-soft);
|
|
2048
2545
|
word-break: break-word;
|
|
2049
|
-
/* Two-line clamp · keeps every row a uniform height so the
|
|
2050
|
-
list scans cleanly. The snippet is already centered on the
|
|
2051
|
-
match (LEAD/TRAIL chars on either side) so the visible
|
|
2052
|
-
lines almost always include the keyword. */
|
|
2053
2546
|
display: -webkit-box;
|
|
2054
2547
|
-webkit-line-clamp: 2;
|
|
2055
2548
|
-webkit-box-orient: vertical;
|
|
2056
2549
|
overflow: hidden;
|
|
2057
2550
|
}
|
|
2058
|
-
.
|
|
2551
|
+
.sr-snippet mark {
|
|
2059
2552
|
background: var(--lime);
|
|
2060
2553
|
color: var(--bg, #0c0c0c);
|
|
2061
2554
|
padding: 0 2px;
|
|
2062
2555
|
border-radius: 2px;
|
|
2063
2556
|
font-weight: 600;
|
|
2064
2557
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
}
|
|
2073
|
-
.search-row-source-label {
|
|
2558
|
+
|
|
2559
|
+
/* Empty / hint card · only ever rendered inside .search-results
|
|
2560
|
+
so it's tied to the has-results state. Centered block, calm
|
|
2561
|
+
copy. */
|
|
2562
|
+
.search-empty {
|
|
2563
|
+
padding: 40px 0;
|
|
2564
|
+
text-align: center;
|
|
2074
2565
|
color: var(--text-faint);
|
|
2075
|
-
|
|
2566
|
+
}
|
|
2567
|
+
.search-empty-kicker {
|
|
2568
|
+
display: block;
|
|
2569
|
+
font-family: var(--mono);
|
|
2570
|
+
font-size: 10px;
|
|
2076
2571
|
letter-spacing: 0.18em;
|
|
2572
|
+
text-transform: uppercase;
|
|
2573
|
+
color: var(--lime);
|
|
2574
|
+
margin-bottom: 8px;
|
|
2077
2575
|
}
|
|
2078
|
-
.search-
|
|
2576
|
+
.search-empty-msg {
|
|
2577
|
+
font-family: var(--font-human);
|
|
2578
|
+
font-size: 14px;
|
|
2079
2579
|
color: var(--text-soft);
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2580
|
+
line-height: 1.5;
|
|
2581
|
+
max-width: 520px;
|
|
2582
|
+
margin: 0 auto;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
/* Reduced-motion · snap state changes, no transitions. */
|
|
2586
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2587
|
+
.search-hero,
|
|
2588
|
+
.search-card,
|
|
2589
|
+
.search-input,
|
|
2590
|
+
.search-input-icon,
|
|
2591
|
+
.search-input-clear,
|
|
2592
|
+
.sr-title { transition: none; }
|
|
2086
2593
|
}
|
|
2087
|
-
.search-row:hover .search-row-source-room { color: var(--lime); }
|
|
2088
2594
|
|
|
2089
2595
|
/* Search-result jump · in-place keyword pulse + article outline.
|
|
2090
2596
|
The keyword wraps in a `.search-keyword-flash` span (~2s lime
|
|
@@ -2259,7 +2765,7 @@
|
|
|
2259
2765
|
spatially, so each fades. */
|
|
2260
2766
|
.notes-item-passage {
|
|
2261
2767
|
font-family: var(--font-human);
|
|
2262
|
-
font-size:
|
|
2768
|
+
font-size: 16px;
|
|
2263
2769
|
line-height: 1.6;
|
|
2264
2770
|
color: var(--text);
|
|
2265
2771
|
margin: 0;
|
|
@@ -2711,8 +3217,12 @@
|
|
|
2711
3217
|
min-width: 22px;
|
|
2712
3218
|
padding: 0 5px;
|
|
2713
3219
|
background: var(--panel-3);
|
|
2714
|
-
|
|
2715
|
-
|
|
3220
|
+
/* Was `var(--text)` (the brightest text token) which read as a
|
|
3221
|
+
second focal point next to the room subject and pulled the
|
|
3222
|
+
eye away from the live header. Drop to `--text-soft` so the
|
|
3223
|
+
chip stays informational (visible but not loud). */
|
|
3224
|
+
color: var(--text-soft);
|
|
3225
|
+
border: 0.5px solid var(--line-bright);
|
|
2716
3226
|
margin-left: -4px;
|
|
2717
3227
|
display: inline-flex;
|
|
2718
3228
|
align-items: center;
|
|
@@ -5889,6 +6399,21 @@
|
|
|
5889
6399
|
invisible at glance. Bumped to text-dim for legibility while
|
|
5890
6400
|
still reading as quieter than the name + model in the same line. */
|
|
5891
6401
|
.msg-tag { color: var(--text-dim); text-transform: lowercase; }
|
|
6402
|
+
/* Excused-from-room marker · the chair removed this director
|
|
6403
|
+
mid-discussion but their past messages still render so the
|
|
6404
|
+
transcript stays coherent. Reads as a muted mono pill in the
|
|
6405
|
+
message header so the reader knows this seat is gone. */
|
|
6406
|
+
.msg-excused {
|
|
6407
|
+
color: var(--text-faint);
|
|
6408
|
+
font-family: var(--mono);
|
|
6409
|
+
font-size: 9.5px;
|
|
6410
|
+
font-weight: 600;
|
|
6411
|
+
letter-spacing: 0.04em;
|
|
6412
|
+
text-transform: lowercase;
|
|
6413
|
+
border: 0.5px dashed var(--line-bright);
|
|
6414
|
+
padding: 1px 6px;
|
|
6415
|
+
cursor: help;
|
|
6416
|
+
}
|
|
5892
6417
|
/* Subtle pill showing which model produced this message. Reads
|
|
5893
6418
|
after the agent name; sits in the same monospace meta line
|
|
5894
6419
|
so it doesn't compete with the conversation. */
|
|
@@ -9337,6 +9862,90 @@
|
|
|
9337
9862
|
margin: 0 auto;
|
|
9338
9863
|
padding: 32px 32px;
|
|
9339
9864
|
width: 100%;
|
|
9865
|
+
/* `isolation: isolate` creates a stacking context here WITHOUT
|
|
9866
|
+
making cmp the containing block for absolute descendants.
|
|
9867
|
+
Result: `.cmp-bg-deco` belongs to cmp's stacking context
|
|
9868
|
+
(so `z-index: -1` puts it just below cmp's static content
|
|
9869
|
+
but ABOVE the lower-layer chat / chat-col panel bgs), while
|
|
9870
|
+
its SPATIAL containing block is `.chat-col` (positioned
|
|
9871
|
+
above) — so the deco can span the full chat-col width
|
|
9872
|
+
without being constrained to cmp's 760px. The two-axis
|
|
9873
|
+
split is what makes the Search-page-style backdrop work
|
|
9874
|
+
inside a max-width-narrower section. */
|
|
9875
|
+
isolation: isolate;
|
|
9876
|
+
}
|
|
9877
|
+
/* 8-bit ambient backdrop · pinned at the top of the composer.
|
|
9878
|
+
Same visual language + position as `.search-bg-deco`. The
|
|
9879
|
+
backdrop ESCAPES `.cmp`'s max-width (760px) so it spans the
|
|
9880
|
+
full main-view width — same `left: 50%; margin-left: -50vw;
|
|
9881
|
+
width: 100vw` pattern the Search page uses. `.main-view`'s
|
|
9882
|
+
own `overflow: hidden` clips it to the visible content area,
|
|
9883
|
+
so "full width" resolves to the room-content rect (sidebar
|
|
9884
|
+
not included). Scene-tuned SVG content is injected by
|
|
9885
|
+
`composerBgDecoSvg(scene)` in app.js · room mode draws a
|
|
9886
|
+
mini boardroom (table + chairs), agent mode draws a row of
|
|
9887
|
+
pixel character heads + speech bubble. */
|
|
9888
|
+
.cmp-bg-deco {
|
|
9889
|
+
/* Containing block is `.chat-col` (set position: relative above)
|
|
9890
|
+
so `top/left/right: 0` snap to the FULL CHAT COLUMN — exactly
|
|
9891
|
+
the right-pane width the user wants. No `100vw` escape · the
|
|
9892
|
+
previous viewport-relative width spilled behind the sidebar
|
|
9893
|
+
because the sidebar lives OUTSIDE this main-view's overflow
|
|
9894
|
+
clip, so the deco rendered under it. Anchoring to `.chat-col`
|
|
9895
|
+
gets us the right pane's actual content rect and nothing
|
|
9896
|
+
beyond it. */
|
|
9897
|
+
position: absolute;
|
|
9898
|
+
top: 0;
|
|
9899
|
+
left: 0;
|
|
9900
|
+
right: 0;
|
|
9901
|
+
height: 280px;
|
|
9902
|
+
pointer-events: none;
|
|
9903
|
+
z-index: -1;
|
|
9904
|
+
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
9905
|
+
mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
9906
|
+
opacity: 0.85;
|
|
9907
|
+
}
|
|
9908
|
+
.cmp-bg-deco svg { display: block; width: 100%; height: 100%; }
|
|
9909
|
+
/* 8-bit deco animations · keep subtle so the field reads as
|
|
9910
|
+
"alive" rather than "demanding attention". All animations are
|
|
9911
|
+
opacity / quantised transforms so the pixel-art register
|
|
9912
|
+
holds (no smooth tweens). Each element gets an inline
|
|
9913
|
+
`animation-delay` from the SVG generator so siblings don't
|
|
9914
|
+
pulse in lockstep — the field shimmers organically. */
|
|
9915
|
+
@keyframes deco-twinkle {
|
|
9916
|
+
0%, 100% { opacity: 1; }
|
|
9917
|
+
50% { opacity: 0.35; }
|
|
9918
|
+
}
|
|
9919
|
+
@keyframes deco-shine {
|
|
9920
|
+
0%, 100% { opacity: 1; }
|
|
9921
|
+
50% { opacity: 0.55; }
|
|
9922
|
+
}
|
|
9923
|
+
@keyframes deco-spark {
|
|
9924
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
9925
|
+
50% { opacity: 0.4; transform: scale(0.7); }
|
|
9926
|
+
}
|
|
9927
|
+
@keyframes deco-bob {
|
|
9928
|
+
0%, 100% { transform: translateY(0); }
|
|
9929
|
+
50% { transform: translateY(-1.5px); }
|
|
9930
|
+
}
|
|
9931
|
+
@keyframes deco-blink {
|
|
9932
|
+
0%, 70%, 100% { opacity: 1; }
|
|
9933
|
+
80%, 92% { opacity: 0.15; }
|
|
9934
|
+
}
|
|
9935
|
+
.cmp-bg-deco .deco-twinkle { animation: deco-twinkle 4.5s ease-in-out infinite; }
|
|
9936
|
+
.cmp-bg-deco .deco-shine { animation: deco-shine 3.2s ease-in-out infinite; }
|
|
9937
|
+
.cmp-bg-deco .deco-spark { animation: deco-spark 2.4s ease-in-out infinite;
|
|
9938
|
+
transform-box: fill-box; transform-origin: center; }
|
|
9939
|
+
.cmp-bg-deco .deco-bob { animation: deco-bob 3.0s ease-in-out infinite;
|
|
9940
|
+
transform-box: fill-box; transform-origin: center; }
|
|
9941
|
+
.cmp-bg-deco .deco-blink { animation: deco-blink 4.0s ease-in-out infinite; }
|
|
9942
|
+
/* Reduced-motion · stop animation but keep the deco visible. */
|
|
9943
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9944
|
+
.cmp-bg-deco .deco-twinkle,
|
|
9945
|
+
.cmp-bg-deco .deco-shine,
|
|
9946
|
+
.cmp-bg-deco .deco-spark,
|
|
9947
|
+
.cmp-bg-deco .deco-bob,
|
|
9948
|
+
.cmp-bg-deco .deco-blink { animation: none; }
|
|
9340
9949
|
}
|
|
9341
9950
|
/* Default composer mode (content fits viewport).
|
|
9342
9951
|
Toggled by JS via `.chat--composer` (added in
|
|
@@ -12399,6 +13008,17 @@
|
|
|
12399
13008
|
overflow: hidden;
|
|
12400
13009
|
background: var(--panel);
|
|
12401
13010
|
height: 100%;
|
|
13011
|
+
/* `position: relative` makes chat-col the CONTAINING BLOCK
|
|
13012
|
+
for `.cmp-bg-deco` (positioned absolute) so the deco's
|
|
13013
|
+
`left/right: 0` snap to the full chat-col width. NOTE: no
|
|
13014
|
+
`isolation` here · we DON'T want chat-col to also be the
|
|
13015
|
+
stacking context. The deco's `z-index: -1` is meant to
|
|
13016
|
+
paint within `.cmp`'s isolated stacking context (which sits
|
|
13017
|
+
on top of chat / chat-col bgs), not get trapped below
|
|
13018
|
+
chat-col's own panel bg. Splitting the two roles lets the
|
|
13019
|
+
deco stretch wide while still rendering above the chat
|
|
13020
|
+
panel — see `.cmp` + `.cmp-bg-deco` below. */
|
|
13021
|
+
position: relative;
|
|
12402
13022
|
}
|
|
12403
13023
|
|
|
12404
13024
|
/* Input bar — sits inside the chat column. Live state hosts a
|
|
@@ -13478,6 +14098,17 @@
|
|
|
13478
14098
|
}
|
|
13479
14099
|
return;
|
|
13480
14100
|
}
|
|
14101
|
+
// "search" → cross-room keyword search view. Restores on
|
|
14102
|
+
// refresh same as reports / notes — without this branch
|
|
14103
|
+
// the saved token fell through to the roomId resolver,
|
|
14104
|
+
// failed `appRoomExists("search")`, and bounced the user
|
|
14105
|
+
// back to the new-room composer.
|
|
14106
|
+
if (sub === "search") {
|
|
14107
|
+
if (window.app && typeof window.app.openSearch === "function") {
|
|
14108
|
+
window.app.openSearch();
|
|
14109
|
+
}
|
|
14110
|
+
return;
|
|
14111
|
+
}
|
|
13481
14112
|
if (!sub || sub === "new") {
|
|
13482
14113
|
if (window.app && typeof window.app.setComposerMode === "function") {
|
|
13483
14114
|
window.app.setComposerMode("room");
|
|
@@ -13580,9 +14211,9 @@
|
|
|
13580
14211
|
// when there's no saved sub-state at all.
|
|
13581
14212
|
const saved = lsGet(ROOMS_KEY);
|
|
13582
14213
|
let target;
|
|
13583
|
-
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes") {
|
|
14214
|
+
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes" && saved !== "search") {
|
|
13584
14215
|
target = saved; // explicit room id
|
|
13585
|
-
} else if (saved === "reports" || saved === "notes" || saved === "new") {
|
|
14216
|
+
} else if (saved === "reports" || saved === "notes" || saved === "new" || saved === "search") {
|
|
13586
14217
|
target = saved; // explicit destination preserved on any nav
|
|
13587
14218
|
} else if (fromTabClick) {
|
|
13588
14219
|
// No saved sub-state · fresh user clicking the tab. Prefer
|