@saasira/holi 0.1.5 → 0.1.6

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/README.md CHANGED
@@ -224,11 +224,22 @@ Rules:
224
224
  - Direct `region[name]` inside a block fills matching nested slots inside that block's assigned subtree.
225
225
  - Missing slots are empty by default.
226
226
  - Set `inherit-missing="true"` on `page` to keep fallback slot content.
227
+ - Use `renderer="browser"` on source pages to show the built-in placeholder until runtime composition completes.
228
+ - After browser or compiler rendering completes, both `renderer` and `rendered` should be removed from the final page node.
227
229
  - Use `layout-src` for an explicit layout file or `layouts-base` for a layout folder.
230
+ - Page-level attributes `title`, `description`, `canonical`, and `lang` are applied to the real document metadata.
231
+ - Page-level `theme` is applied to the real `<body>` element for theme scoping and switching.
228
232
  - Use `<layout-head data-layout-head="true">` for shared head assets in layouts.
229
233
  - Use `<tail data-layout-tail="true">` for deferred body-end assets in layouts.
230
234
  - Non-script nodes from `layout-head` go to the real HTML `<head>`.
231
235
  - Nodes from `tail` are appended near the end of `<body>`.
236
+ - Built-in region `meta` always targets the real HTML `<head>`.
237
+ - Built-in regions `styles` and `page-styles` always target the real HTML `<head>`, even when the layout does not declare those slots.
238
+ - Built-in regions `scripts` and `page-scripts` always target the end of `<body>`, even when the layout does not declare those slots.
239
+ - Use `styles` for external asset nodes such as `<link rel="stylesheet" href="...">`.
240
+ - Use `scripts` for external asset nodes such as `<script src="..."></script>`.
241
+ - Use `page-styles` for inline `<style>` tags.
242
+ - Use `page-scripts` for inline `<script>` tags.
232
243
 
233
244
  ## Holi vs React / Angular / Vue
234
245
 
@@ -262,3 +273,36 @@ Practical summary:
262
273
  - Auto init can be disabled with `window.HoliAutoInit = false` before loading `dist/holi.js`.
263
274
  - Component templates are loaded from `dist/components.html` (with runtime fallback paths).
264
275
  - Layout templates are loaded from `dist/layouts.html` (with runtime fallback paths).
276
+
277
+ ## OfflineIndicator
278
+
279
+ `OfflineIndicator` provides a template-driven connectivity banner with automatic polling and queue count display.
280
+
281
+ Supported declaration styles:
282
+
283
+ - `<offline></offline>`
284
+ - `<section component="offline"></section>`
285
+ - `<section role="offline"></section>`
286
+ - `[data-offline]`
287
+
288
+ Useful attributes:
289
+
290
+ - `scope="page|block|inline"`
291
+ - `position="top-left|top-right|bottom-left|bottom-right|top-center|bottom-center"`
292
+ - `host="#selector"` for `scope="block"`
293
+ - `ping-url="/api/ping"`
294
+ - `heartbeat-ms="2500"`
295
+ - `probe-timeout-ms="1800"`
296
+ - `duration="1200"`
297
+ - `max-attempts="4"`
298
+
299
+ Instance/static helpers:
300
+
301
+ - `el.offlineIndicator.retryConnection()`
302
+ - `el.offlineIndicator.simulateOffline()`
303
+ - `el.offlineIndicator.simulateOnline()`
304
+ - `window.OfflineIndicator.queue(payload)`
305
+ - `window.OfflineIndicator.clearQueue()`
306
+ - `window.OfflineIndicator.create(options)`
307
+
308
+ See [offline example](/D:/Work/Self/Holi/src/examples/pages/offline.html) for page-scoped, block-scoped, and queue-sync scenarios.
@@ -96,6 +96,15 @@
96
96
  </template>
97
97
 
98
98
 
99
+ <!-- Template: themeswitcher.html src/templates/components/themeswitcher.html -->
100
+ <template id="themeswitcher-template">
101
+ <section class="holi-themeswitcher">
102
+ <label data-role="theme-label" for="holi-theme-switcher-select">@{label}</label>
103
+ <select id="holi-theme-switcher-select" data-role="theme-select"></select>
104
+ </section>
105
+ </template>
106
+
107
+
99
108
  <!-- Template: textarea.html src/templates/components/textarea.html -->
100
109
  <template id="textarea-field-template">
101
110
  <div class="holi-textarea" role="group">
@@ -231,6 +240,36 @@
231
240
  </template>
232
241
 
233
242
 
243
+ <!-- Template: refresh.html src/templates/components/refresh.html -->
244
+ <template id="refresh-template">
245
+ <section class="holi-refresh" data-state="idle" tabindex="0">
246
+ <div class="holi-refresh__handle" data-role="handle" aria-hidden="true">
247
+ <div class="holi-refresh__icon">
248
+ <span class="holi-refresh__icon-arrow"></span>
249
+ <span class="holi-refresh__icon-spinner"></span>
250
+ </div>
251
+ <div class="holi-refresh__label" data-role="label">Pull to refresh</div>
252
+ <div class="holi-refresh__progress">
253
+ <span class="holi-refresh__progress-bar" data-role="progress"></span>
254
+ </div>
255
+ </div>
256
+ <div class="holi-refresh__content" data-role="content"></div>
257
+ </section>
258
+ </template>
259
+
260
+
261
+ <!-- Template: rating.html src/templates/components/rating.html -->
262
+ <template id="rating-template">
263
+ <div class="rating" data-role="rating-root" data-rating="0">
264
+ <i class="fa fa-star-o" data-value="1" aria-hidden="true"></i>
265
+ <i class="fa fa-star-o" data-value="2" aria-hidden="true"></i>
266
+ <i class="fa fa-star-o" data-value="3" aria-hidden="true"></i>
267
+ <i class="fa fa-star-o" data-value="4" aria-hidden="true"></i>
268
+ <i class="fa fa-star-o" data-value="5" aria-hidden="true"></i>
269
+ </div>
270
+ </template>
271
+
272
+
234
273
  <!-- Template: radio.html src/templates/components/radio.html -->
235
274
  <template id="radio-group-template">
236
275
  <fieldset class="holi-radio-group">
@@ -254,6 +293,21 @@
254
293
  </template>
255
294
 
256
295
 
296
+ <!-- Template: pwa.html src/templates/components/pwa.html -->
297
+ <!-- Theme color meta (dynamic via JS) -->
298
+ <meta name="theme-color" content="#2563eb">
299
+
300
+ <!-- iOS Safari -->
301
+ <link rel="apple-touch-icon" href="/icons/icon-192.png">
302
+ <meta name="apple-mobile-web-app-capable" content="yes">
303
+ <meta name="apple-mobile-web-app-status-bar-style" content="default">
304
+ <meta name="apple-mobile-web-app-title" content="HF Components">
305
+
306
+ <!-- Microsoft/Edge -->
307
+ <meta name="msapplication-TileImage" content="/icons/icon-192.png">
308
+ <meta name="msapplication-TileColor" content="#2563eb">
309
+
310
+
257
311
  <!-- Template: progress.html src/templates/components/progress.html -->
258
312
  <template id="progress">
259
313
  <section class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100">
@@ -280,6 +334,24 @@
280
334
  </template>
281
335
 
282
336
 
337
+ <!-- Template: offline.html src/templates/components/offline.html -->
338
+ <template id="offline-template">
339
+ <section class="holi-offline-indicator" data-status="online" data-visible="false" hidden aria-live="polite" aria-atomic="true" role="status">
340
+ <div class="holi-offline-indicator__badge" aria-hidden="true">
341
+ <span class="holi-offline-indicator__dot"></span>
342
+ </div>
343
+ <div class="holi-offline-indicator__message">
344
+ <span class="holi-offline-indicator__status" data-role="status-text">Offline</span>
345
+ <span class="holi-offline-indicator__meta">
346
+ <span class="holi-offline-indicator__sync" data-role="sync-text">0 pending</span>
347
+ <span class="holi-offline-indicator__attempts" data-role="attempt-text">Retry 0/5</span>
348
+ </span>
349
+ </div>
350
+ <button type="button" class="holi-offline-indicator__retry" data-action="retry" aria-label="Retry connection">Retry</button>
351
+ </section>
352
+ </template>
353
+
354
+
283
355
  <!-- Template: menubar.html src/templates/components/menubar.html -->
284
356
  <template id="menubar-template">
285
357
  <nav class="holi-menubar" data-mode="bar" aria-label="Site menu">
@@ -289,12 +361,28 @@
289
361
  </template>
290
362
 
291
363
 
364
+ <!-- Template: localeswitcher.html src/templates/components/localeswitcher.html -->
365
+ <template id="localeswitcher-template">
366
+ <section class="holi-localeswitcher">
367
+ <label data-role="locale-label" for="holi-locale-switcher-select">@{label}</label>
368
+ <select id="holi-locale-switcher-select" data-role="locale-select"></select>
369
+ </section>
370
+ </template>
371
+
372
+
292
373
  <!-- Template: loader.html src/templates/components/loader.html -->
293
374
  <template id="loader-template">
294
375
  <section class="holi-loader" data-scope="inline" hidden>
295
376
  <div class="holi-loader-backdrop"></div>
296
377
  <div class="holi-loader-body" role="status" aria-live="polite" aria-atomic="true">
297
- <span class="holi-loader-spinner" aria-hidden="true"></span>
378
+ <span class="holi-loader-spinner" data-role="loader-spinner" data-type="spinner" aria-hidden="true">
379
+ <span class="holi-loader-spinner-ring" aria-hidden="true"></span>
380
+ <span class="holi-loader-dots" aria-hidden="true">
381
+ <span class="holi-loader-dot"></span>
382
+ <span class="holi-loader-dot"></span>
383
+ <span class="holi-loader-dot"></span>
384
+ </span>
385
+ </span>
298
386
  <span class="holi-loader-message" data-role="loader-message">Loading...</span>
299
387
  </div>
300
388
  </section>
package/dist/holi.css CHANGED
@@ -591,6 +591,21 @@
591
591
  font-size: 0.88rem;
592
592
  }
593
593
 
594
+ .rating {
595
+ display: inline-flex;
596
+ align-items: center;
597
+ gap: 4px;
598
+ line-height: 1;
599
+ }
600
+
601
+ .rating .fa {
602
+ color: #f59e0b;
603
+ }
604
+
605
+ .rating .fa-star-o {
606
+ color: #cbd5e1;
607
+ }
608
+
594
609
  .holi.datagrid {
595
610
  border: 1px solid #d9dee8;
596
611
  border-radius: 8px;
@@ -1934,17 +1949,415 @@ body.holi-drawer-lock {
1934
1949
  .holi-loader-spinner {
1935
1950
  width: 14px;
1936
1951
  height: 14px;
1952
+ display: inline-grid;
1953
+ place-items: center;
1954
+ flex: 0 0 auto;
1955
+ }
1956
+
1957
+ .holi-loader-spinner-ring {
1958
+ width: 100%;
1959
+ height: 100%;
1937
1960
  border-radius: 50%;
1938
1961
  border: 2px solid #cbd5e1;
1939
1962
  border-top-color: #2563eb;
1940
1963
  animation: holi-loader-spin 0.8s linear infinite;
1941
1964
  }
1942
1965
 
1966
+ .holi-loader-dots {
1967
+ display: none;
1968
+ align-items: center;
1969
+ gap: 3px;
1970
+ }
1971
+
1972
+ .holi-loader-dot {
1973
+ width: 4px;
1974
+ height: 4px;
1975
+ border-radius: 50%;
1976
+ background: #2563eb;
1977
+ animation: holi-loader-bounce 0.9s ease-in-out infinite;
1978
+ }
1979
+
1980
+ .holi-loader-dot:nth-child(2) {
1981
+ animation-delay: 0.15s;
1982
+ }
1983
+
1984
+ .holi-loader-dot:nth-child(3) {
1985
+ animation-delay: 0.3s;
1986
+ }
1987
+
1988
+ .holi-loader[data-type="dots"] .holi-loader-spinner-ring {
1989
+ display: none;
1990
+ }
1991
+
1992
+ .holi-loader[data-type="dots"] .holi-loader-dots {
1993
+ display: inline-flex;
1994
+ }
1995
+
1996
+ .holi-loader[data-size="sm"] .holi-loader-body {
1997
+ gap: 6px;
1998
+ padding: 6px 10px;
1999
+ font-size: 12px;
2000
+ }
2001
+
2002
+ .holi-loader[data-size="sm"] .holi-loader-spinner {
2003
+ width: 12px;
2004
+ height: 12px;
2005
+ }
2006
+
2007
+ .holi-loader[data-size="sm"] .holi-loader-spinner-ring {
2008
+ border-width: 2px;
2009
+ }
2010
+
2011
+ .holi-loader[data-size="sm"] .holi-loader-dot {
2012
+ width: 3px;
2013
+ height: 3px;
2014
+ }
2015
+
2016
+ .holi-loader[data-size="lg"] .holi-loader-body {
2017
+ gap: 10px;
2018
+ padding: 10px 14px;
2019
+ font-size: 14px;
2020
+ }
2021
+
2022
+ .holi-loader[data-size="lg"] .holi-loader-spinner {
2023
+ width: 18px;
2024
+ height: 18px;
2025
+ }
2026
+
2027
+ .holi-loader[data-size="lg"] .holi-loader-spinner-ring {
2028
+ border-width: 3px;
2029
+ }
2030
+
2031
+ .holi-loader[data-size="lg"] .holi-loader-dot {
2032
+ width: 5px;
2033
+ height: 5px;
2034
+ }
2035
+
1943
2036
  @keyframes holi-loader-spin {
1944
2037
  from { transform: rotate(0deg); }
1945
2038
  to { transform: rotate(360deg); }
1946
2039
  }
1947
2040
 
2041
+ @keyframes holi-loader-bounce {
2042
+ 0%, 80%, 100% {
2043
+ transform: scale(0.7);
2044
+ opacity: 0.45;
2045
+ }
2046
+ 40% {
2047
+ transform: scale(1);
2048
+ opacity: 1;
2049
+ }
2050
+ }
2051
+
2052
+ .holi-offline-indicator {
2053
+ position: fixed;
2054
+ right: 16px;
2055
+ bottom: 16px;
2056
+ z-index: 1200;
2057
+ display: inline-flex;
2058
+ align-items: center;
2059
+ gap: 12px;
2060
+ padding: 12px 14px;
2061
+ border: 1px solid #f59e0b;
2062
+ border-radius: 16px;
2063
+ background: rgba(15, 23, 42, 0.95);
2064
+ color: #f8fafc;
2065
+ box-shadow: 0 18px 40px rgba(15, 23, 42, 0.28);
2066
+ backdrop-filter: blur(10px);
2067
+ }
2068
+
2069
+ .holi-offline-indicator[data-scope="inline"] {
2070
+ position: relative;
2071
+ inset: auto;
2072
+ width: 100%;
2073
+ justify-content: space-between;
2074
+ }
2075
+
2076
+ .holi-offline-indicator[data-scope="block"] {
2077
+ position: absolute;
2078
+ }
2079
+
2080
+ .holi-offline-indicator[data-scope="inline"][data-position],
2081
+ .holi-offline-indicator[data-scope="block"][data-position] {
2082
+ top: auto;
2083
+ right: auto;
2084
+ bottom: auto;
2085
+ left: auto;
2086
+ }
2087
+
2088
+ .holi-offline-indicator[data-position="top-right"] {
2089
+ top: 16px;
2090
+ right: 16px;
2091
+ }
2092
+
2093
+ .holi-offline-indicator[data-position="top-left"] {
2094
+ top: 16px;
2095
+ left: 16px;
2096
+ }
2097
+
2098
+ .holi-offline-indicator[data-position="bottom-right"] {
2099
+ bottom: 16px;
2100
+ right: 16px;
2101
+ }
2102
+
2103
+ .holi-offline-indicator[data-position="bottom-left"] {
2104
+ bottom: 16px;
2105
+ left: 16px;
2106
+ }
2107
+
2108
+ .holi-offline-indicator[data-position="top-center"] {
2109
+ top: 16px;
2110
+ left: 50%;
2111
+ transform: translateX(-50%);
2112
+ }
2113
+
2114
+ .holi-offline-indicator[data-position="bottom-center"] {
2115
+ bottom: 16px;
2116
+ left: 50%;
2117
+ transform: translateX(-50%);
2118
+ }
2119
+
2120
+ .holi-offline-indicator[hidden] {
2121
+ display: none;
2122
+ }
2123
+
2124
+ .holi-offline-indicator__badge {
2125
+ width: 22px;
2126
+ height: 22px;
2127
+ display: inline-grid;
2128
+ place-items: center;
2129
+ border-radius: 999px;
2130
+ background: rgba(248, 113, 113, 0.18);
2131
+ }
2132
+
2133
+ .holi-offline-indicator__dot {
2134
+ width: 10px;
2135
+ height: 10px;
2136
+ border-radius: 50%;
2137
+ background: #f97316;
2138
+ box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.16);
2139
+ }
2140
+
2141
+ .holi-offline-indicator[data-status="online"] .holi-offline-indicator__badge {
2142
+ background: rgba(16, 185, 129, 0.18);
2143
+ }
2144
+
2145
+ .holi-offline-indicator[data-status="online"] .holi-offline-indicator__dot {
2146
+ background: #10b981;
2147
+ box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.16);
2148
+ }
2149
+
2150
+ .holi-offline-indicator__message {
2151
+ display: grid;
2152
+ gap: 2px;
2153
+ }
2154
+
2155
+ .holi-offline-indicator__meta {
2156
+ display: flex;
2157
+ flex-wrap: wrap;
2158
+ gap: 8px;
2159
+ }
2160
+
2161
+ .holi-offline-indicator__status {
2162
+ font-weight: 700;
2163
+ letter-spacing: 0.01em;
2164
+ }
2165
+
2166
+ .holi-offline-indicator__sync {
2167
+ color: #cbd5e1;
2168
+ font-size: 12px;
2169
+ }
2170
+
2171
+ .holi-offline-indicator__attempts {
2172
+ color: #fdba74;
2173
+ font-size: 12px;
2174
+ }
2175
+
2176
+ .holi-offline-indicator__retry {
2177
+ border: 0;
2178
+ border-radius: 999px;
2179
+ padding: 8px 12px;
2180
+ background: #f8fafc;
2181
+ color: #0f172a;
2182
+ font-weight: 600;
2183
+ cursor: pointer;
2184
+ }
2185
+
2186
+ .holi-offline-indicator__retry:hover,
2187
+ .holi-offline-indicator__retry:focus-visible {
2188
+ background: #e2e8f0;
2189
+ outline: none;
2190
+ }
2191
+
2192
+ .holi-offline-indicator__retry:disabled {
2193
+ opacity: 0.5;
2194
+ cursor: default;
2195
+ }
2196
+
2197
+ body.app--offline {
2198
+ outline: 4px solid rgba(249, 115, 22, 0.22);
2199
+ outline-offset: -4px;
2200
+ }
2201
+
2202
+ @media (max-width: 640px) {
2203
+ .holi-offline-indicator[data-scope="page"] {
2204
+ left: 12px;
2205
+ right: 12px;
2206
+ bottom: 12px;
2207
+ justify-content: space-between;
2208
+ }
2209
+ }
2210
+
2211
+ .holi-refresh {
2212
+ --refresh-pull-distance: 0px;
2213
+ position: relative;
2214
+ display: block;
2215
+ min-height: 100px;
2216
+ outline: none;
2217
+ }
2218
+
2219
+ .holi-refresh__handle {
2220
+ position: absolute;
2221
+ top: 0;
2222
+ left: 0;
2223
+ right: 0;
2224
+ display: grid;
2225
+ justify-items: center;
2226
+ gap: 8px;
2227
+ padding: 14px 16px 10px;
2228
+ transform: translateY(calc(-100% + var(--refresh-pull-distance)));
2229
+ transition: transform 180ms ease;
2230
+ color: #475569;
2231
+ pointer-events: none;
2232
+ }
2233
+
2234
+ .holi-refresh[data-state="pulling"] .holi-refresh__handle,
2235
+ .holi-refresh[data-state="armed"] .holi-refresh__handle,
2236
+ .holi-refresh[data-state="refreshing"] .holi-refresh__handle,
2237
+ .holi-refresh[data-state="complete"] .holi-refresh__handle {
2238
+ transition: transform 120ms ease;
2239
+ }
2240
+
2241
+ .holi-refresh__icon {
2242
+ width: 34px;
2243
+ height: 34px;
2244
+ display: grid;
2245
+ place-items: center;
2246
+ border-radius: 999px;
2247
+ background: linear-gradient(180deg, #ffffff, #e2e8f0);
2248
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.14);
2249
+ }
2250
+
2251
+ .holi-refresh__icon-arrow,
2252
+ .holi-refresh__icon-spinner {
2253
+ grid-area: 1 / 1;
2254
+ }
2255
+
2256
+ .holi-refresh__icon-arrow {
2257
+ width: 12px;
2258
+ height: 12px;
2259
+ border-right: 2px solid #0f172a;
2260
+ border-bottom: 2px solid #0f172a;
2261
+ transform: rotate(45deg);
2262
+ transition: transform 140ms ease, opacity 140ms ease;
2263
+ }
2264
+
2265
+ .holi-refresh[data-state="armed"] .holi-refresh__icon-arrow {
2266
+ transform: rotate(-135deg);
2267
+ }
2268
+
2269
+ .holi-refresh__icon-spinner {
2270
+ width: 14px;
2271
+ height: 14px;
2272
+ border-radius: 50%;
2273
+ border: 2px solid rgba(37, 99, 235, 0.18);
2274
+ border-top-color: #2563eb;
2275
+ opacity: 0;
2276
+ }
2277
+
2278
+ .holi-refresh[data-state="refreshing"] .holi-refresh__icon-arrow,
2279
+ .holi-refresh[data-state="complete"] .holi-refresh__icon-arrow {
2280
+ opacity: 0;
2281
+ }
2282
+
2283
+ .holi-refresh[data-state="refreshing"] .holi-refresh__icon-spinner {
2284
+ opacity: 1;
2285
+ animation: holi-refresh-spin 0.8s linear infinite;
2286
+ }
2287
+
2288
+ .holi-refresh__label {
2289
+ font-size: 13px;
2290
+ font-weight: 600;
2291
+ letter-spacing: 0.01em;
2292
+ }
2293
+
2294
+ .holi-refresh__progress {
2295
+ width: min(160px, 44vw);
2296
+ height: 3px;
2297
+ border-radius: 999px;
2298
+ background: rgba(148, 163, 184, 0.26);
2299
+ overflow: hidden;
2300
+ }
2301
+
2302
+ .holi-refresh__progress-bar {
2303
+ display: block;
2304
+ width: 100%;
2305
+ height: 100%;
2306
+ transform-origin: left center;
2307
+ transform: scaleX(var(--refresh-progress, 0));
2308
+ background: linear-gradient(90deg, #0f766e, #2563eb);
2309
+ transition: transform 120ms ease;
2310
+ }
2311
+
2312
+ .holi-refresh__content {
2313
+ position: relative;
2314
+ min-height: inherit;
2315
+ transition: transform 180ms ease;
2316
+ transform: translateY(calc(var(--refresh-pull-distance) * 0.48));
2317
+ will-change: transform;
2318
+ }
2319
+
2320
+ .holi-refresh[data-state="refreshing"] .holi-refresh__content {
2321
+ transform: translateY(34px);
2322
+ }
2323
+
2324
+ .holi-refresh[data-state="complete"] .holi-refresh__icon {
2325
+ background: linear-gradient(180deg, #ecfdf5, #d1fae5);
2326
+ }
2327
+
2328
+ .holi-refresh[data-state="complete"] .holi-refresh__label {
2329
+ color: #047857;
2330
+ }
2331
+
2332
+ .holi-refresh[data-state="disabled"] {
2333
+ opacity: 0.7;
2334
+ }
2335
+
2336
+ @keyframes holi-refresh-spin {
2337
+ from { transform: rotate(0deg); }
2338
+ to { transform: rotate(360deg); }
2339
+ }
2340
+
2341
+ .holi-localeswitcher {
2342
+ display: inline-grid;
2343
+ gap: 8px;
2344
+ min-width: 180px;
2345
+ }
2346
+
2347
+ .holi-localeswitcher label {
2348
+ font-size: 0.9rem;
2349
+ font-weight: 600;
2350
+ }
2351
+
2352
+ .holi-localeswitcher select {
2353
+ min-width: 180px;
2354
+ padding: 8px 10px;
2355
+ border: 1px solid #cfc4b5;
2356
+ border-radius: 8px;
2357
+ background: #fffdfa;
2358
+ color: inherit;
2359
+ }
2360
+
1948
2361
  .holi-menubar {
1949
2362
  position: relative;
1950
2363
  --menubar-border: #d6dde8;
@@ -2146,6 +2559,62 @@ body.holi-drawer-lock {
2146
2559
  margin-top: 0;
2147
2560
  }
2148
2561
 
2562
+ page[renderer="browser"]:not([rendered]),
2563
+ page[renderer="browser"][rendered="pending"] {
2564
+ display: block;
2565
+ position: relative;
2566
+ min-height: 70vh;
2567
+ padding: 24px;
2568
+ border: 1px solid #ddd6c8;
2569
+ background:
2570
+ linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(247, 241, 232, 0.92)),
2571
+ linear-gradient(90deg, #efe6d8, #f7f1e8);
2572
+ overflow: hidden;
2573
+ }
2574
+
2575
+ page[renderer="browser"]:not([rendered]) > *,
2576
+ page[renderer="browser"][rendered="pending"] > * {
2577
+ display: none !important;
2578
+ }
2579
+
2580
+ page[renderer="browser"]:not([rendered])::before,
2581
+ page[renderer="browser"][rendered="pending"]::before {
2582
+ content: "";
2583
+ display: block;
2584
+ width: min(420px, 82%);
2585
+ height: 44px;
2586
+ border-radius: 12px;
2587
+ background: linear-gradient(90deg, #e8dccb, #f6efe5, #e8dccb);
2588
+ background-size: 220% 100%;
2589
+ animation: holi-page-placeholder 1.6s linear infinite;
2590
+ }
2591
+
2592
+ page[renderer="browser"]:not([rendered])::after,
2593
+ page[renderer="browser"][rendered="pending"]::after {
2594
+ content: "";
2595
+ display: block;
2596
+ margin-top: 20px;
2597
+ width: 100%;
2598
+ height: calc(70vh - 88px);
2599
+ min-height: 260px;
2600
+ border-radius: 18px;
2601
+ background:
2602
+ linear-gradient(180deg, rgba(214, 202, 187, 0.5), rgba(255, 255, 255, 0)),
2603
+ linear-gradient(90deg, #eadfce 18%, #f8f2e9 44%, #eadfce 70%);
2604
+ background-size: 240% 100%;
2605
+ animation: holi-page-placeholder 1.9s linear infinite;
2606
+ }
2607
+
2608
+ @keyframes holi-page-placeholder {
2609
+ from {
2610
+ background-position: 200% 0;
2611
+ }
2612
+
2613
+ to {
2614
+ background-position: -40% 0;
2615
+ }
2616
+ }
2617
+
2149
2618
  .holi-panel {
2150
2619
  --holi-panel-border: #dce3ea;
2151
2620
  --holi-panel-bg: #ffffff;
@@ -2653,6 +3122,26 @@ body.holi-drawer-lock {
2653
3122
  font-size: 0.92rem;
2654
3123
  }
2655
3124
 
3125
+ .holi-themeswitcher {
3126
+ display: inline-grid;
3127
+ gap: 8px;
3128
+ min-width: 180px;
3129
+ }
3130
+
3131
+ .holi-themeswitcher label {
3132
+ font-size: 0.9rem;
3133
+ font-weight: 600;
3134
+ }
3135
+
3136
+ .holi-themeswitcher select {
3137
+ min-width: 180px;
3138
+ padding: 8px 10px;
3139
+ border: 1px solid #cfc4b5;
3140
+ border-radius: 8px;
3141
+ background: #fffdfa;
3142
+ color: inherit;
3143
+ }
3144
+
2656
3145
  .holi-tree {
2657
3146
  border: 1px solid #d6dde8;
2658
3147
  border-radius: 10px;