@obvi/blueprint 1.1.0 → 1.1.1
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 +1 -0
- package/dist/blueprint.css +1039 -1
- package/dist/blueprint.js +24 -10
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A blueprint is a technical document that should look like one: monochrome ink on
|
|
|
11
11
|
- **Semantic HTML is the API.** Headings, paragraphs, lists, tables, figures, and landmarks render as a finished document with no classes to memorize. You write meaning; the design arrives for free.
|
|
12
12
|
- **Classless-first, not class-only.** Components exist only for the few patterns native HTML cannot express, and each is a composition of real semantic elements rather than a replacement for them.
|
|
13
13
|
- **Zero-specificity by contract.** Everything ships in cascade layers wrapped in `:where()`, so the library never fights your own CSS. Your styles always win, with no `!important` and no parent-scoping.
|
|
14
|
+
- **Runtime-complete package.** The default stylesheet includes the chrome that `blueprint.js` injects, and runtime icons are inline SVG, so installed documents do not depend on repository-only CSS or assets.
|
|
14
15
|
|
|
15
16
|
The result is one portable stylesheet, no build step, that turns plain semantic markup into a polished blueprint.
|
|
16
17
|
|
package/dist/blueprint.css
CHANGED
|
@@ -2814,9 +2814,12 @@
|
|
|
2814
2814
|
padding-bottom: var(--bp-shell-inset-block);
|
|
2815
2815
|
}
|
|
2816
2816
|
/* <main> is the paper sheet spanning the full column between the vertical
|
|
2817
|
-
frame rules; direct children keep the prose measure from Tier 1b.
|
|
2817
|
+
frame rules; direct children keep the prose measure from Tier 1b. Keep the
|
|
2818
|
+
shell out of the overflow/scroll chain so viewport-fixed navigation nested
|
|
2819
|
+
by stored Blueprints is not clipped by WebKit. The page owns scrolling. */
|
|
2818
2820
|
:where(html[data-bp-document] main) {
|
|
2819
2821
|
max-width: none;
|
|
2822
|
+
overflow: visible;
|
|
2820
2823
|
margin: 0 var(--bp-shell-inset-right) 0 var(--bp-shell-inset-left);
|
|
2821
2824
|
min-height: calc(
|
|
2822
2825
|
100vh - var(--bp-shell-inset-top) - var(--bp-shell-inset-block)
|
|
@@ -4192,3 +4195,1038 @@
|
|
|
4192
4195
|
}
|
|
4193
4196
|
}
|
|
4194
4197
|
}
|
|
4198
|
+
|
|
4199
|
+
/* Shared document chrome. This stays separate in source so the docs site can
|
|
4200
|
+
iterate on it independently, but the npm build appends it to the default
|
|
4201
|
+
blueprint.css export because blueprint.js injects these runtime elements. */
|
|
4202
|
+
|
|
4203
|
+
:root {
|
|
4204
|
+
--bp-site-nav-height: 44px;
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
/* Only the pages that still use the top bar (e.g. the code demo) reserve
|
|
4208
|
+
space for it. The main docs page drops it in favor of the sidebar
|
|
4209
|
+
blueprint switcher + bottom-left theme toggle below. */
|
|
4210
|
+
body:has(.site-nav) {
|
|
4211
|
+
padding-top: var(--bp-site-nav-height);
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
/* Frame and shell top inset align below the site nav. */
|
|
4215
|
+
html[data-bp-document]:has(.site-nav) {
|
|
4216
|
+
--bp-shell-inset-top: calc(var(--bp-site-nav-height) + var(--bp-shell-inset-block));
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4219
|
+
.site-nav {
|
|
4220
|
+
position: fixed;
|
|
4221
|
+
inset: 0 0 auto;
|
|
4222
|
+
z-index: 400;
|
|
4223
|
+
display: flex;
|
|
4224
|
+
align-items: stretch;
|
|
4225
|
+
height: var(--bp-site-nav-height);
|
|
4226
|
+
padding: 0 16px;
|
|
4227
|
+
border-bottom: 1px solid var(--bp-edge);
|
|
4228
|
+
background: var(--bp-paper);
|
|
4229
|
+
color: var(--bp-text);
|
|
4230
|
+
font-family: var(--bp-mono, var(--mono, ui-monospace, monospace));
|
|
4231
|
+
font-size: 11px;
|
|
4232
|
+
letter-spacing: 0.08em;
|
|
4233
|
+
text-transform: uppercase;
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
.site-nav__brand,
|
|
4237
|
+
.site-nav a {
|
|
4238
|
+
display: flex;
|
|
4239
|
+
align-items: center;
|
|
4240
|
+
min-height: 0;
|
|
4241
|
+
padding: 0 12px;
|
|
4242
|
+
color: var(--bp-text-secondary);
|
|
4243
|
+
text-decoration: none;
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
.site-nav__brand {
|
|
4247
|
+
gap: 8px;
|
|
4248
|
+
padding-left: 0;
|
|
4249
|
+
color: var(--bp-ink);
|
|
4250
|
+
font-weight: 650;
|
|
4251
|
+
letter-spacing: 0.04em;
|
|
4252
|
+
text-transform: none;
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
.site-nav__logo {
|
|
4256
|
+
width: 20px;
|
|
4257
|
+
height: 20px;
|
|
4258
|
+
flex: 0 0 20px;
|
|
4259
|
+
color: var(--bp-ink);
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
.site-nav__links {
|
|
4263
|
+
display: flex;
|
|
4264
|
+
align-items: stretch;
|
|
4265
|
+
gap: 2px;
|
|
4266
|
+
margin: 0 0 0 12px;
|
|
4267
|
+
padding: 0;
|
|
4268
|
+
list-style: none;
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
.site-nav__links > li {
|
|
4272
|
+
display: flex;
|
|
4273
|
+
margin: 0;
|
|
4274
|
+
line-height: 1;
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
.site-nav a:hover,
|
|
4278
|
+
.site-nav a[aria-current="page"] {
|
|
4279
|
+
color: var(--bp-ink);
|
|
4280
|
+
background: var(--bp-fill-amb);
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4283
|
+
.site-nav a:focus-visible,
|
|
4284
|
+
.site-nav button:focus-visible {
|
|
4285
|
+
outline: 2px solid var(--bp-ink);
|
|
4286
|
+
outline-offset: -2px;
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
body > .site-nav .site-nav__theme {
|
|
4290
|
+
position: static;
|
|
4291
|
+
inset: auto;
|
|
4292
|
+
z-index: auto;
|
|
4293
|
+
align-self: center;
|
|
4294
|
+
margin: 0 0 0 auto;
|
|
4295
|
+
padding: 6px 10px;
|
|
4296
|
+
border: 1px solid var(--bp-ink);
|
|
4297
|
+
border-radius: var(--bp-radius-0, 0);
|
|
4298
|
+
background: oklch(0 0 0 / 0);
|
|
4299
|
+
color: var(--bp-ink);
|
|
4300
|
+
font-family: inherit;
|
|
4301
|
+
font-size: inherit;
|
|
4302
|
+
letter-spacing: inherit;
|
|
4303
|
+
line-height: 1;
|
|
4304
|
+
text-transform: uppercase;
|
|
4305
|
+
cursor: pointer;
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
body > .site-nav .site-nav__theme:hover {
|
|
4309
|
+
background: var(--bp-fill-amb);
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
/* The docs sidebar remains fixed, but begins below the global site nav. */
|
|
4313
|
+
.site-nav ~ .bp-sidebar {
|
|
4314
|
+
top: var(--bp-site-nav-height);
|
|
4315
|
+
height: calc(100vh - var(--bp-site-nav-height));
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4318
|
+
.site-nav + .scroll-progress {
|
|
4319
|
+
position: fixed;
|
|
4320
|
+
top: calc(var(--bp-site-nav-height)+ 4px);
|
|
4321
|
+
right: 0;
|
|
4322
|
+
left: 0;
|
|
4323
|
+
height: 2px;
|
|
4324
|
+
background: var(--bp-ink);
|
|
4325
|
+
transform: scaleX(0);
|
|
4326
|
+
transform-origin: left;
|
|
4327
|
+
z-index: 500;
|
|
4328
|
+
pointer-events: none;
|
|
4329
|
+
transition: transform var(--bp-duration-instant) var(--bp-ease-linear);
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
@media (max-width: 560px) {
|
|
4333
|
+
:root {
|
|
4334
|
+
--bp-site-nav-height: 40px;
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
.site-nav {
|
|
4338
|
+
padding: 0 8px;
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
.site-nav__brand {
|
|
4342
|
+
padding: 0 8px 0 0;
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
.site-nav__wordmark {
|
|
4346
|
+
display: none;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
.site-nav__links {
|
|
4350
|
+
margin-left: 0;
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
.site-nav a {
|
|
4354
|
+
padding: 0 8px;
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
body > .site-nav .site-nav__theme {
|
|
4358
|
+
padding: 6px 8px;
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
/* =====================================================================
|
|
4363
|
+
Blueprint collection switcher (sidebar header) + bottom-left theme toggle
|
|
4364
|
+
---------------------------------------------------------------------
|
|
4365
|
+
Docs chrome that replaces the old top bar: the sidebar wordmark becomes
|
|
4366
|
+
a menu for jumping between blueprints, and the theme control becomes a
|
|
4367
|
+
line icon pinned to the bottom-left corner.
|
|
4368
|
+
===================================================================== */
|
|
4369
|
+
|
|
4370
|
+
/* Reset the library <details>/<summary> chrome for the switcher. */
|
|
4371
|
+
.doc-switcher {
|
|
4372
|
+
position: relative;
|
|
4373
|
+
margin: 0 0 var(--bp-space-3);
|
|
4374
|
+
padding: 0;
|
|
4375
|
+
border: 0;
|
|
4376
|
+
background: none;
|
|
4377
|
+
}
|
|
4378
|
+
/* Shared field chrome for the switcher trigger and the search input so they
|
|
4379
|
+
read as one control family (border, radius, surface + hover/focus states). */
|
|
4380
|
+
.sidebar-field {
|
|
4381
|
+
border: 1px solid var(--bp-edge);
|
|
4382
|
+
border-radius: var(--bp-radius-6, 6px);
|
|
4383
|
+
background: var(--bp-paper);
|
|
4384
|
+
/* Hover-only color motion — idle fields snap on theme flip so the root
|
|
4385
|
+
view-transition crossfade isn't fighting per-field token tweens. */
|
|
4386
|
+
transition: none;
|
|
4387
|
+
}
|
|
4388
|
+
.sidebar-field:hover,
|
|
4389
|
+
.sidebar-field:focus-within {
|
|
4390
|
+
border-color: var(--bp-ink-line);
|
|
4391
|
+
background: var(--bp-fill-amb);
|
|
4392
|
+
transition:
|
|
4393
|
+
border-color var(--bp-duration-fast) var(--bp-ease-out),
|
|
4394
|
+
background-color var(--bp-duration-fast) var(--bp-ease-out);
|
|
4395
|
+
}
|
|
4396
|
+
.doc-switcher__current {
|
|
4397
|
+
display: flex;
|
|
4398
|
+
align-items: center;
|
|
4399
|
+
justify-content: space-between;
|
|
4400
|
+
gap: var(--bp-space-3);
|
|
4401
|
+
margin: 0;
|
|
4402
|
+
/* Match the sidebar toggle box so the two controls align on one row. */
|
|
4403
|
+
height: var(--bp-sidebar-toggle-size);
|
|
4404
|
+
padding: 0 var(--bp-space-3);
|
|
4405
|
+
list-style: none;
|
|
4406
|
+
font-weight: 400;
|
|
4407
|
+
cursor: pointer;
|
|
4408
|
+
}
|
|
4409
|
+
.doc-switcher__current::-webkit-details-marker {
|
|
4410
|
+
display: none;
|
|
4411
|
+
}
|
|
4412
|
+
.doc-switcher__current::before {
|
|
4413
|
+
content: none !important;
|
|
4414
|
+
}
|
|
4415
|
+
.doc-switcher__label {
|
|
4416
|
+
display: flex;
|
|
4417
|
+
flex-direction: column;
|
|
4418
|
+
gap: var(--bp-space-1);
|
|
4419
|
+
min-width: 0;
|
|
4420
|
+
}
|
|
4421
|
+
.doc-switcher__title {
|
|
4422
|
+
font-size: var(--bp-text-small);
|
|
4423
|
+
line-height: 1.3;
|
|
4424
|
+
font-weight: var(--bp-weight-medium);
|
|
4425
|
+
color: var(--bp-text);
|
|
4426
|
+
}
|
|
4427
|
+
.doc-switcher__meta {
|
|
4428
|
+
display: none;
|
|
4429
|
+
}
|
|
4430
|
+
.doc-switcher__chevron {
|
|
4431
|
+
display: inline-flex;
|
|
4432
|
+
flex: 0 0 auto;
|
|
4433
|
+
color: var(--bp-text-secondary);
|
|
4434
|
+
}
|
|
4435
|
+
.doc-switcher[open] .doc-switcher__chevron {
|
|
4436
|
+
transform: rotate(180deg);
|
|
4437
|
+
}
|
|
4438
|
+
.doc-switcher__menu {
|
|
4439
|
+
position: absolute;
|
|
4440
|
+
left: 0;
|
|
4441
|
+
right: 0;
|
|
4442
|
+
top: calc(100% + var(--bp-space-1));
|
|
4443
|
+
z-index: 300;
|
|
4444
|
+
display: flex;
|
|
4445
|
+
flex-direction: column;
|
|
4446
|
+
gap: var(--bp-space-1);
|
|
4447
|
+
padding: var(--bp-space-1);
|
|
4448
|
+
border: 1px solid var(--bp-edge);
|
|
4449
|
+
/* Container radius − padding = item radius, so the rows nest perfectly. */
|
|
4450
|
+
border-radius: var(--bp-radius-8, 8px);
|
|
4451
|
+
background: var(--bp-paper);
|
|
4452
|
+
box-shadow: var(--bp-shadow-pop);
|
|
4453
|
+
transform-origin: top center;
|
|
4454
|
+
}
|
|
4455
|
+
/* Open animation: keyframes re-run every time [open] is set on the <details>,
|
|
4456
|
+
unlike @starting-style which only fires on first render. Close stays instant
|
|
4457
|
+
(native <details> hides content immediately). Timing uses motion tokens. */
|
|
4458
|
+
.doc-switcher[open] .doc-switcher__menu {
|
|
4459
|
+
animation: bp-doc-switcher-in var(--bp-duration-normal) var(--bp-ease-out);
|
|
4460
|
+
}
|
|
4461
|
+
@keyframes bp-doc-switcher-in {
|
|
4462
|
+
from {
|
|
4463
|
+
opacity: 0;
|
|
4464
|
+
transform: translateY(-4px) scale(0.98);
|
|
4465
|
+
}
|
|
4466
|
+
to {
|
|
4467
|
+
opacity: 1;
|
|
4468
|
+
transform: none;
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
.doc-switcher__menu a {
|
|
4472
|
+
display: flex;
|
|
4473
|
+
align-items: center;
|
|
4474
|
+
justify-content: space-between;
|
|
4475
|
+
gap: var(--bp-space-2);
|
|
4476
|
+
padding: var(--bp-space-2) var(--bp-space-3);
|
|
4477
|
+
border-radius: var(--bp-radius-4, 4px);
|
|
4478
|
+
text-decoration: none;
|
|
4479
|
+
}
|
|
4480
|
+
.doc-switcher__menu a:hover {
|
|
4481
|
+
background: var(--bp-fill-amb);
|
|
4482
|
+
}
|
|
4483
|
+
.doc-switcher__menu a.is-current {
|
|
4484
|
+
background: var(--bp-fill-hi);
|
|
4485
|
+
}
|
|
4486
|
+
.doc-switcher__option {
|
|
4487
|
+
display: flex;
|
|
4488
|
+
flex-direction: column;
|
|
4489
|
+
min-width: 0;
|
|
4490
|
+
}
|
|
4491
|
+
.doc-switcher__name {
|
|
4492
|
+
font-size: var(--bp-text-small);
|
|
4493
|
+
line-height: 1.3;
|
|
4494
|
+
font-weight: var(--bp-weight-medium);
|
|
4495
|
+
color: var(--bp-text);
|
|
4496
|
+
}
|
|
4497
|
+
.doc-switcher__desc {
|
|
4498
|
+
overflow: hidden;
|
|
4499
|
+
font-size: var(--bp-label-md);
|
|
4500
|
+
line-height: 1.4;
|
|
4501
|
+
color: var(--bp-text-secondary);
|
|
4502
|
+
white-space: nowrap;
|
|
4503
|
+
text-overflow: ellipsis;
|
|
4504
|
+
}
|
|
4505
|
+
/* Reused chevron as a quiet "go to" affordance — only on the current and
|
|
4506
|
+
hovered rows so the resting menu stays clean and condensed. */
|
|
4507
|
+
.doc-switcher__go {
|
|
4508
|
+
flex: 0 0 auto;
|
|
4509
|
+
display: inline-flex;
|
|
4510
|
+
color: var(--bp-text-secondary);
|
|
4511
|
+
opacity: 0;
|
|
4512
|
+
transform: rotate(-90deg);
|
|
4513
|
+
}
|
|
4514
|
+
.doc-switcher__menu a:hover .doc-switcher__go,
|
|
4515
|
+
.doc-switcher__menu a.is-current .doc-switcher__go {
|
|
4516
|
+
opacity: 1;
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
/* Sidebar search — header row beside the corner toggle, above the header scrim. */
|
|
4520
|
+
.sidebar-search {
|
|
4521
|
+
position: relative;
|
|
4522
|
+
/* Sit above the header scrim (z 200) so the field stays crisp while the TOC
|
|
4523
|
+
fades behind it. */
|
|
4524
|
+
z-index: 300;
|
|
4525
|
+
/* Left margin clears the fixed corner toggle so search sits on the same row,
|
|
4526
|
+
beside it. Right edge stops at the content edge so it lines up with the
|
|
4527
|
+
TOC entry pills below. */
|
|
4528
|
+
margin: calc(-1 * var(--bp-space-2)) 0 var(--bp-space-3)
|
|
4529
|
+
calc(var(--bp-sidebar-toggle-size) + var(--bp-space-3));
|
|
4530
|
+
}
|
|
4531
|
+
/* Trigger button: magnifier + label + ⌘K hint on one row. It is a <button>
|
|
4532
|
+
(it opens the search dialog, it is not a text field); the .sidebar-field
|
|
4533
|
+
chrome carries the border/radius/surface and children align inside it. */
|
|
4534
|
+
.sidebar-search__field {
|
|
4535
|
+
display: flex;
|
|
4536
|
+
align-items: center;
|
|
4537
|
+
gap: var(--bp-space-2);
|
|
4538
|
+
width: 100%;
|
|
4539
|
+
height: var(--bp-sidebar-toggle-size);
|
|
4540
|
+
padding: 0 var(--bp-space-3);
|
|
4541
|
+
font: inherit;
|
|
4542
|
+
text-align: left;
|
|
4543
|
+
color: inherit;
|
|
4544
|
+
cursor: pointer;
|
|
4545
|
+
-webkit-appearance: none;
|
|
4546
|
+
appearance: none;
|
|
4547
|
+
}
|
|
4548
|
+
.sidebar-search__field:focus-visible {
|
|
4549
|
+
outline: 2px solid var(--bp-ink);
|
|
4550
|
+
outline-offset: 2px;
|
|
4551
|
+
}
|
|
4552
|
+
.sidebar-search__icon {
|
|
4553
|
+
flex: 0 0 auto;
|
|
4554
|
+
display: inline-flex;
|
|
4555
|
+
color: var(--bp-text-secondary);
|
|
4556
|
+
}
|
|
4557
|
+
.sidebar-search__label {
|
|
4558
|
+
flex: 1 1 auto;
|
|
4559
|
+
min-width: 0;
|
|
4560
|
+
font-family: var(--bp-sans);
|
|
4561
|
+
font-size: var(--bp-text-small);
|
|
4562
|
+
line-height: 1.3;
|
|
4563
|
+
font-weight: 400;
|
|
4564
|
+
color: var(--bp-text-secondary);
|
|
4565
|
+
}
|
|
4566
|
+
/* ⌘K affordance — quiet keycaps, sized down from the base kbd for the rail. */
|
|
4567
|
+
.sidebar-search__hint {
|
|
4568
|
+
flex: 0 0 auto;
|
|
4569
|
+
display: inline-flex;
|
|
4570
|
+
gap: 2px;
|
|
4571
|
+
}
|
|
4572
|
+
.sidebar-search__hint kbd {
|
|
4573
|
+
padding: 0 4px;
|
|
4574
|
+
border-width: 1px;
|
|
4575
|
+
border-radius: var(--bp-radius-2, 2px);
|
|
4576
|
+
font-size: var(--bp-label-md);
|
|
4577
|
+
line-height: 1.5;
|
|
4578
|
+
color: var(--bp-text-secondary);
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4581
|
+
/* =====================================================================
|
|
4582
|
+
Search command menu (docs chrome) — Mintlify-style palette
|
|
4583
|
+
---------------------------------------------------------------------
|
|
4584
|
+
Opened from the sidebar trigger or ⌘K / Ctrl+K (see wireSearchPalette).
|
|
4585
|
+
Reuses the rounded sidebar control family: paper surface, --bp-radius-8
|
|
4586
|
+
panel with --bp-shadow-pop, --bp-radius-4 rows, the --bp-fill-amb /
|
|
4587
|
+
--bp-fill-hi hover + selected surfaces, and the shared open animation.
|
|
4588
|
+
Chrome stays on the neutral ink scale. Query filtering is a plain
|
|
4589
|
+
substring match over the page's headings; richer search is deferred.
|
|
4590
|
+
===================================================================== */
|
|
4591
|
+
|
|
4592
|
+
/* Modal scrim. Hidden until .is-open; centers the panel near the top of the
|
|
4593
|
+
viewport, the way command palettes sit. Sits above all docs chrome. Uses
|
|
4594
|
+
the same drafting scrim as .bp-lightbox (--bp-scrim + --bp-hatch). */
|
|
4595
|
+
.docs-search-overlay {
|
|
4596
|
+
position: fixed;
|
|
4597
|
+
inset: 0;
|
|
4598
|
+
z-index: 600;
|
|
4599
|
+
display: none;
|
|
4600
|
+
justify-content: center;
|
|
4601
|
+
align-items: flex-start;
|
|
4602
|
+
padding: clamp(var(--bp-space-5), 12vh, var(--bp-space-7)) var(--bp-space-4)
|
|
4603
|
+
var(--bp-space-4);
|
|
4604
|
+
background-color: var(--bp-scrim);
|
|
4605
|
+
background-image: var(--bp-hatch);
|
|
4606
|
+
}
|
|
4607
|
+
.docs-search-overlay.is-open {
|
|
4608
|
+
display: flex;
|
|
4609
|
+
animation: bp-search-overlay-in var(--bp-duration-fast, 120ms)
|
|
4610
|
+
var(--bp-ease-out);
|
|
4611
|
+
}
|
|
4612
|
+
.docs-search-overlay.is-open .docs-search {
|
|
4613
|
+
animation: bp-doc-switcher-in var(--bp-duration-normal) var(--bp-ease-out);
|
|
4614
|
+
}
|
|
4615
|
+
@keyframes bp-search-overlay-in {
|
|
4616
|
+
from {
|
|
4617
|
+
opacity: 0;
|
|
4618
|
+
}
|
|
4619
|
+
to {
|
|
4620
|
+
opacity: 1;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4624
|
+
/* The panel. Position-agnostic: the overlay wrapper centers it over a scrim. */
|
|
4625
|
+
.docs-search {
|
|
4626
|
+
display: flex;
|
|
4627
|
+
flex-direction: column;
|
|
4628
|
+
width: min(640px, 100%);
|
|
4629
|
+
max-height: min(560px, 80vh);
|
|
4630
|
+
overflow: hidden;
|
|
4631
|
+
border: 1px solid var(--bp-edge);
|
|
4632
|
+
border-radius: var(--bp-radius-8, 8px);
|
|
4633
|
+
background: var(--bp-paper);
|
|
4634
|
+
box-shadow: var(--bp-shadow-pop);
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
/* Search row */
|
|
4638
|
+
.docs-search__field {
|
|
4639
|
+
display: flex;
|
|
4640
|
+
align-items: center;
|
|
4641
|
+
gap: var(--bp-space-3);
|
|
4642
|
+
padding: var(--bp-space-3) var(--bp-space-4);
|
|
4643
|
+
border-bottom: 1px solid var(--bp-edge);
|
|
4644
|
+
}
|
|
4645
|
+
.docs-search__icon {
|
|
4646
|
+
flex: 0 0 auto;
|
|
4647
|
+
display: inline-flex;
|
|
4648
|
+
color: var(--bp-text-secondary);
|
|
4649
|
+
}
|
|
4650
|
+
.docs-search__input {
|
|
4651
|
+
flex: 1 1 auto;
|
|
4652
|
+
min-width: 0;
|
|
4653
|
+
margin: 0;
|
|
4654
|
+
padding: 0;
|
|
4655
|
+
border: 0;
|
|
4656
|
+
background: none;
|
|
4657
|
+
font-family: var(--bp-sans);
|
|
4658
|
+
font-size: var(--bp-text-body);
|
|
4659
|
+
line-height: var(--bp-lh-body);
|
|
4660
|
+
color: var(--bp-text);
|
|
4661
|
+
-webkit-appearance: none;
|
|
4662
|
+
appearance: none;
|
|
4663
|
+
}
|
|
4664
|
+
.docs-search__input::placeholder {
|
|
4665
|
+
color: var(--bp-text-secondary);
|
|
4666
|
+
}
|
|
4667
|
+
.docs-search__input:focus {
|
|
4668
|
+
outline: none;
|
|
4669
|
+
}
|
|
4670
|
+
.docs-search__input::-webkit-search-decoration,
|
|
4671
|
+
.docs-search__input::-webkit-search-cancel-button {
|
|
4672
|
+
-webkit-appearance: none;
|
|
4673
|
+
}
|
|
4674
|
+
.docs-search__esc {
|
|
4675
|
+
flex: 0 0 auto;
|
|
4676
|
+
padding: 1px 6px;
|
|
4677
|
+
font-size: var(--bp-label-md);
|
|
4678
|
+
letter-spacing: var(--bp-label-md-ls);
|
|
4679
|
+
text-transform: uppercase;
|
|
4680
|
+
color: var(--bp-text-secondary);
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
/* Results region */
|
|
4684
|
+
.docs-search__results {
|
|
4685
|
+
flex: 1 1 auto;
|
|
4686
|
+
overflow-y: auto;
|
|
4687
|
+
padding: var(--bp-space-2);
|
|
4688
|
+
}
|
|
4689
|
+
/* Group label — machine voice (mono + uppercase) with a trailing count. */
|
|
4690
|
+
.docs-search__group {
|
|
4691
|
+
display: flex;
|
|
4692
|
+
align-items: center;
|
|
4693
|
+
gap: var(--bp-space-2);
|
|
4694
|
+
padding: var(--bp-space-2) var(--bp-space-3) var(--bp-space-1);
|
|
4695
|
+
font-family: var(--bp-mono);
|
|
4696
|
+
font-size: var(--bp-label-md);
|
|
4697
|
+
letter-spacing: var(--bp-label-md-ls);
|
|
4698
|
+
text-transform: uppercase;
|
|
4699
|
+
color: var(--bp-text-secondary);
|
|
4700
|
+
}
|
|
4701
|
+
.docs-search__group-count {
|
|
4702
|
+
color: var(--bp-text-secondary);
|
|
4703
|
+
opacity: 0.7;
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4706
|
+
/* A result row: breadcrumb · title · snippet, with a trailing return key.
|
|
4707
|
+
Rows are <a> for navigation; suppress the base prose link underline except
|
|
4708
|
+
on the selected/hovered surface (background carries the affordance). */
|
|
4709
|
+
.docs-search__row {
|
|
4710
|
+
display: grid;
|
|
4711
|
+
grid-template-columns: 1fr auto;
|
|
4712
|
+
align-items: center;
|
|
4713
|
+
gap: var(--bp-space-1) var(--bp-space-3);
|
|
4714
|
+
padding: var(--bp-space-2) var(--bp-space-3);
|
|
4715
|
+
border-radius: var(--bp-radius-4, 4px);
|
|
4716
|
+
cursor: pointer;
|
|
4717
|
+
text-decoration: none;
|
|
4718
|
+
color: inherit;
|
|
4719
|
+
}
|
|
4720
|
+
.docs-search__row:hover {
|
|
4721
|
+
background: var(--bp-fill-amb);
|
|
4722
|
+
text-decoration: none;
|
|
4723
|
+
}
|
|
4724
|
+
/* Keyboard-selected row — reuses the rail's "current" surface. */
|
|
4725
|
+
.docs-search__row[aria-selected="true"] {
|
|
4726
|
+
background: var(--bp-fill-hi);
|
|
4727
|
+
text-decoration: none;
|
|
4728
|
+
}
|
|
4729
|
+
.docs-search__row-main {
|
|
4730
|
+
display: flex;
|
|
4731
|
+
flex-direction: column;
|
|
4732
|
+
gap: var(--bp-space-1);
|
|
4733
|
+
min-width: 0;
|
|
4734
|
+
}
|
|
4735
|
+
.docs-search__crumb {
|
|
4736
|
+
display: block;
|
|
4737
|
+
font-family: var(--bp-mono);
|
|
4738
|
+
font-size: var(--bp-label-md);
|
|
4739
|
+
letter-spacing: var(--bp-label-md-ls);
|
|
4740
|
+
color: var(--bp-text-secondary);
|
|
4741
|
+
white-space: nowrap;
|
|
4742
|
+
overflow: hidden;
|
|
4743
|
+
text-overflow: ellipsis;
|
|
4744
|
+
}
|
|
4745
|
+
.docs-search__title {
|
|
4746
|
+
display: flex;
|
|
4747
|
+
align-items: baseline;
|
|
4748
|
+
gap: var(--bp-space-1);
|
|
4749
|
+
min-width: 0;
|
|
4750
|
+
font-family: var(--bp-sans);
|
|
4751
|
+
font-size: var(--bp-text-small);
|
|
4752
|
+
line-height: 1.3;
|
|
4753
|
+
font-weight: var(--bp-weight-medium);
|
|
4754
|
+
color: var(--bp-text);
|
|
4755
|
+
}
|
|
4756
|
+
/* The "#" anchor glyph borrows the machine voice so it reads as a heading
|
|
4757
|
+
link, not prose. */
|
|
4758
|
+
.docs-search__hash {
|
|
4759
|
+
flex: 0 0 auto;
|
|
4760
|
+
font-family: var(--bp-mono);
|
|
4761
|
+
color: var(--bp-text-secondary);
|
|
4762
|
+
}
|
|
4763
|
+
.docs-search__snippet {
|
|
4764
|
+
overflow: hidden;
|
|
4765
|
+
font-size: var(--bp-text-small);
|
|
4766
|
+
line-height: 1.4;
|
|
4767
|
+
color: var(--bp-text-secondary);
|
|
4768
|
+
white-space: nowrap;
|
|
4769
|
+
text-overflow: ellipsis;
|
|
4770
|
+
}
|
|
4771
|
+
.docs-search__snippet mark {
|
|
4772
|
+
padding: 0;
|
|
4773
|
+
background: var(--bp-highlight);
|
|
4774
|
+
color: var(--bp-text);
|
|
4775
|
+
}
|
|
4776
|
+
/* Return-key affordance — only on the selected/hovered row, like the
|
|
4777
|
+
switcher's quiet "go to" chevron. */
|
|
4778
|
+
.docs-search__enter {
|
|
4779
|
+
flex: 0 0 auto;
|
|
4780
|
+
align-self: center;
|
|
4781
|
+
padding: 1px 5px;
|
|
4782
|
+
font-size: var(--bp-label-md);
|
|
4783
|
+
color: var(--bp-text-secondary);
|
|
4784
|
+
opacity: 0;
|
|
4785
|
+
}
|
|
4786
|
+
.docs-search__row:hover .docs-search__enter,
|
|
4787
|
+
.docs-search__row[aria-selected="true"] .docs-search__enter {
|
|
4788
|
+
opacity: 1;
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4791
|
+
/* Empty state */
|
|
4792
|
+
.docs-search__empty {
|
|
4793
|
+
display: flex;
|
|
4794
|
+
flex-direction: column;
|
|
4795
|
+
align-items: center;
|
|
4796
|
+
gap: var(--bp-space-2);
|
|
4797
|
+
padding: var(--bp-space-6) var(--bp-space-4);
|
|
4798
|
+
text-align: center;
|
|
4799
|
+
}
|
|
4800
|
+
.docs-search__empty-icon {
|
|
4801
|
+
display: inline-flex;
|
|
4802
|
+
color: var(--bp-text-secondary);
|
|
4803
|
+
opacity: 0.6;
|
|
4804
|
+
}
|
|
4805
|
+
.docs-search__empty-title {
|
|
4806
|
+
font-family: var(--bp-sans);
|
|
4807
|
+
font-size: var(--bp-text-small);
|
|
4808
|
+
font-weight: var(--bp-weight-medium);
|
|
4809
|
+
color: var(--bp-text);
|
|
4810
|
+
}
|
|
4811
|
+
.docs-search__empty-note {
|
|
4812
|
+
font-size: var(--bp-text-small);
|
|
4813
|
+
color: var(--bp-text-secondary);
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4816
|
+
/* Footer keyboard legend */
|
|
4817
|
+
.docs-search__foot {
|
|
4818
|
+
display: flex;
|
|
4819
|
+
flex-wrap: wrap;
|
|
4820
|
+
align-items: center;
|
|
4821
|
+
justify-content: space-between;
|
|
4822
|
+
gap: var(--bp-space-1) var(--bp-space-3);
|
|
4823
|
+
padding: var(--bp-space-2) var(--bp-space-4);
|
|
4824
|
+
border-top: 1px solid var(--bp-edge);
|
|
4825
|
+
font-family: var(--bp-mono);
|
|
4826
|
+
font-size: var(--bp-label-md);
|
|
4827
|
+
letter-spacing: var(--bp-label-md-ls);
|
|
4828
|
+
color: var(--bp-text-secondary);
|
|
4829
|
+
}
|
|
4830
|
+
.docs-search__keys {
|
|
4831
|
+
display: flex;
|
|
4832
|
+
flex-wrap: wrap;
|
|
4833
|
+
align-items: center;
|
|
4834
|
+
gap: var(--bp-space-1) var(--bp-space-3);
|
|
4835
|
+
}
|
|
4836
|
+
.docs-search__key {
|
|
4837
|
+
display: inline-flex;
|
|
4838
|
+
align-items: center;
|
|
4839
|
+
gap: var(--bp-space-1);
|
|
4840
|
+
}
|
|
4841
|
+
.docs-search__foot kbd {
|
|
4842
|
+
padding: 0 4px;
|
|
4843
|
+
border-width: 1px;
|
|
4844
|
+
border-radius: var(--bp-radius-2, 2px);
|
|
4845
|
+
font-size: var(--bp-label-md);
|
|
4846
|
+
line-height: 1.5;
|
|
4847
|
+
color: var(--bp-text-secondary);
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
/* Sidebar footer scrim token. The footer (theme switch + author/date) gets a
|
|
4851
|
+
soft fade above it so the scrollable TOC dissolves into the rail rather than
|
|
4852
|
+
colliding with the footer. The bottom band stays solid to back the footer.
|
|
4853
|
+
Interpolating `in oklch` is premultiplied, so a plain `oklch(0 0 0 / 0)` stop
|
|
4854
|
+
fades cleanly (the zero-alpha endpoint contributes no colour, no muddy
|
|
4855
|
+
mid-tone) — and our rail bg is achromatic, so no same-colour stop is needed.
|
|
4856
|
+
The plain gradient syntax stays as the fallback for engines without oklch
|
|
4857
|
+
gradient interpolation. */
|
|
4858
|
+
:root {
|
|
4859
|
+
/* Shared square control size for the corner toggle, doc-switcher trigger,
|
|
4860
|
+
theme switch, and scrim geometry — one token keeps every calc in sync. */
|
|
4861
|
+
--bp-sidebar-toggle-size: 30px;
|
|
4862
|
+
/* Content column inset — toggle, TOC pills, switcher, and footer share one
|
|
4863
|
+
left/right edge so the rail reads as an even column. */
|
|
4864
|
+
--bp-sidebar-content-inset: calc(var(--bp-space-4) + var(--bp-space-3));
|
|
4865
|
+
/* Frame line → chrome row: same gap below the top line and above the bottom. */
|
|
4866
|
+
--bp-sidebar-chrome-gap: var(--bp-space-3);
|
|
4867
|
+
--bp-sidebar-chrome-inset-top: calc(
|
|
4868
|
+
var(--bp-shell-inset-top, var(--bp-space-4)) + var(--bp-sidebar-chrome-gap)
|
|
4869
|
+
);
|
|
4870
|
+
--bp-sidebar-chrome-inset-bottom: calc(
|
|
4871
|
+
var(--bp-shell-inset-block, var(--bp-space-4)) + var(--bp-sidebar-chrome-gap)
|
|
4872
|
+
);
|
|
4873
|
+
--bp-sidebar-footer: calc(
|
|
4874
|
+
var(--bp-sidebar-chrome-inset-bottom) + var(--bp-sidebar-toggle-size)
|
|
4875
|
+
);
|
|
4876
|
+
/* Scroll clearance: footer band + breathing room for the last TOC pill. */
|
|
4877
|
+
--bp-sidebar-scroll-padding-bottom: calc(
|
|
4878
|
+
var(--bp-sidebar-footer) + var(--bp-space-4)
|
|
4879
|
+
);
|
|
4880
|
+
--bp-sidebar-fade: linear-gradient(
|
|
4881
|
+
to top,
|
|
4882
|
+
var(--bp-bg) var(--bp-sidebar-footer),
|
|
4883
|
+
oklch(0 0 0 / 0)
|
|
4884
|
+
);
|
|
4885
|
+
|
|
4886
|
+
/* Header counterpart: a circular scrim centred on the corner toggle so the
|
|
4887
|
+
TOC dissolves *around* the icon as it scrolls up under it — radiating from
|
|
4888
|
+
the icon rather than a flat top edge. Centre = toggle inset + half its box.
|
|
4889
|
+
Solid out to --bp-space-4, then fades over --bp-space-6. */
|
|
4890
|
+
--bp-sidebar-header-scrim: calc(
|
|
4891
|
+
var(--bp-sidebar-icon-center) + var(--bp-space-4) + var(--bp-space-6)
|
|
4892
|
+
);
|
|
4893
|
+
--bp-sidebar-icon-center: calc(
|
|
4894
|
+
var(--bp-sidebar-chrome-inset-top) + var(--bp-sidebar-toggle-size) / 2
|
|
4895
|
+
);
|
|
4896
|
+
--bp-sidebar-icon-fade: radial-gradient(
|
|
4897
|
+
circle at var(--bp-sidebar-icon-center) var(--bp-sidebar-icon-center),
|
|
4898
|
+
var(--bp-bg) var(--bp-space-4),
|
|
4899
|
+
oklch(0 0 0 / 0) calc(var(--bp-space-4) + var(--bp-space-6))
|
|
4900
|
+
);
|
|
4901
|
+
}
|
|
4902
|
+
@supports (
|
|
4903
|
+
background:
|
|
4904
|
+
linear-gradient(in oklch, oklch(0.5 0.1 30), oklch(0.5 0.1 240))
|
|
4905
|
+
) {
|
|
4906
|
+
:root {
|
|
4907
|
+
--bp-sidebar-fade: linear-gradient(
|
|
4908
|
+
in oklch to top,
|
|
4909
|
+
var(--bp-bg) var(--bp-sidebar-footer),
|
|
4910
|
+
oklch(0 0 0 / 0)
|
|
4911
|
+
);
|
|
4912
|
+
--bp-sidebar-icon-fade: radial-gradient(
|
|
4913
|
+
in oklch circle at var(--bp-sidebar-icon-center)
|
|
4914
|
+
var(--bp-sidebar-icon-center),
|
|
4915
|
+
var(--bp-bg) var(--bp-space-4),
|
|
4916
|
+
oklch(0 0 0 / 0) calc(var(--bp-space-4) + var(--bp-space-6))
|
|
4917
|
+
);
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
/* Sidebar footer: the theme switch and the author/date block read as two flex
|
|
4922
|
+
items with space between them, spanning the same band as the TOC entries
|
|
4923
|
+
(left edge on the pill, right edge at the rail's content edge). */
|
|
4924
|
+
.sidebar-footer {
|
|
4925
|
+
display: flex;
|
|
4926
|
+
align-items: center;
|
|
4927
|
+
justify-content: space-between;
|
|
4928
|
+
gap: var(--bp-space-3);
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
/* Author / date metadata — grouped as the footer's right-hand flex item. */
|
|
4932
|
+
.sidebar-meta {
|
|
4933
|
+
display: flex;
|
|
4934
|
+
flex-direction: row;
|
|
4935
|
+
align-items: center;
|
|
4936
|
+
gap: var(--bp-space-4);
|
|
4937
|
+
}
|
|
4938
|
+
.sidebar-meta__item {
|
|
4939
|
+
display: flex;
|
|
4940
|
+
flex-direction: column;
|
|
4941
|
+
gap: 0;
|
|
4942
|
+
}
|
|
4943
|
+
.sidebar-meta__item .bp-meta {
|
|
4944
|
+
margin: 0;
|
|
4945
|
+
line-height: 1.25;
|
|
4946
|
+
}
|
|
4947
|
+
.sidebar-meta__item .bp-note {
|
|
4948
|
+
margin: 0;
|
|
4949
|
+
line-height: 1.35;
|
|
4950
|
+
}
|
|
4951
|
+
|
|
4952
|
+
/* Leave room at the bottom of the rail so the last entry clears the footer. */
|
|
4953
|
+
.bp-sidebar {
|
|
4954
|
+
--bp-theme-toggle-height: var(--bp-sidebar-toggle-size);
|
|
4955
|
+
padding-bottom: var(--bp-sidebar-scroll-padding-bottom);
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
/* Bottom-left theme switch (Iconoir half-moon / light-bulb). Scoped to the
|
|
4959
|
+
sidebar so the code demo's in-nav theme button is unaffected. */
|
|
4960
|
+
.bp-sidebar .theme-toggle {
|
|
4961
|
+
/* A flex child of .sidebar-footer; relative so the thumb anchors to it. */
|
|
4962
|
+
position: relative;
|
|
4963
|
+
flex: 0 0 auto;
|
|
4964
|
+
display: grid;
|
|
4965
|
+
grid-template-columns: 1fr 1fr;
|
|
4966
|
+
align-items: center;
|
|
4967
|
+
width: 54px;
|
|
4968
|
+
height: var(--bp-theme-toggle-height);
|
|
4969
|
+
padding: 2px;
|
|
4970
|
+
border: 1px solid var(--bp-edge);
|
|
4971
|
+
border-radius: var(--bp-radius-6, 6px);
|
|
4972
|
+
background: var(--bp-fill-amb);
|
|
4973
|
+
color: var(--bp-text-secondary);
|
|
4974
|
+
cursor: pointer;
|
|
4975
|
+
transition: none;
|
|
4976
|
+
}
|
|
4977
|
+
.bp-sidebar .theme-toggle:hover {
|
|
4978
|
+
border-color: var(--bp-ink-line);
|
|
4979
|
+
background: var(--bp-paper);
|
|
4980
|
+
transition:
|
|
4981
|
+
border-color var(--bp-duration-fast) var(--bp-ease-out),
|
|
4982
|
+
background-color var(--bp-duration-fast) var(--bp-ease-out),
|
|
4983
|
+
color var(--bp-duration-fast) var(--bp-ease-out);
|
|
4984
|
+
}
|
|
4985
|
+
.bp-sidebar .theme-toggle:focus-visible {
|
|
4986
|
+
outline: 2px solid var(--bp-ink);
|
|
4987
|
+
outline-offset: 2px;
|
|
4988
|
+
}
|
|
4989
|
+
.bp-sidebar .theme-toggle__thumb {
|
|
4990
|
+
position: absolute;
|
|
4991
|
+
top: 2px;
|
|
4992
|
+
left: 2px;
|
|
4993
|
+
/* height − (2×border + 2×pad) keeps an even 2px gap on every side. */
|
|
4994
|
+
width: calc(var(--bp-theme-toggle-height) - 6px);
|
|
4995
|
+
height: calc(var(--bp-theme-toggle-height) - 6px);
|
|
4996
|
+
border: 1px solid var(--bp-ink-line);
|
|
4997
|
+
border-radius: var(--bp-radius-4, 4px);
|
|
4998
|
+
background: var(--bp-paper);
|
|
4999
|
+
box-shadow: 0 1px 2px oklch(0 0 0 / 0.06);
|
|
5000
|
+
pointer-events: none;
|
|
5001
|
+
}
|
|
5002
|
+
[data-obvious-theme="dark"] .bp-sidebar .theme-toggle__thumb {
|
|
5003
|
+
transform: translateX(24px);
|
|
5004
|
+
}
|
|
5005
|
+
.bp-sidebar .theme-toggle svg {
|
|
5006
|
+
display: block;
|
|
5007
|
+
}
|
|
5008
|
+
.bp-sidebar .theme-toggle__icon {
|
|
5009
|
+
position: relative;
|
|
5010
|
+
z-index: 1;
|
|
5011
|
+
display: flex;
|
|
5012
|
+
align-items: center;
|
|
5013
|
+
justify-content: center;
|
|
5014
|
+
pointer-events: none;
|
|
5015
|
+
}
|
|
5016
|
+
:not([data-obvious-theme="dark"]) .bp-sidebar .theme-toggle__icon--moon,
|
|
5017
|
+
[data-obvious-theme="dark"] .bp-sidebar .theme-toggle__icon--bulb {
|
|
5018
|
+
color: var(--bp-ink);
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
/* =====================================================================
|
|
5022
|
+
Sidebar collapse toggle (Iconoir sidebar glyph, pinned top-left)
|
|
5023
|
+
---------------------------------------------------------------------
|
|
5024
|
+
Docs chrome that hides/shows the fixed contents rail, like the doc
|
|
5025
|
+
switcher and theme toggle above. It lives on <body> (not inside the
|
|
5026
|
+
rail) so it stays reachable once the rail is gone. The collapsed-state
|
|
5027
|
+
rules are unlayered author CSS, so they win over blueprint.css per the
|
|
5028
|
+
specificity contract. The whole rail slides as one rigid unit — its scrims
|
|
5029
|
+
+ footer ride along (no opacity tricks), and the sheet trails one space-4
|
|
5030
|
+
behind the rail's edge the entire way (shared duration + easing).
|
|
5031
|
+
===================================================================== */
|
|
5032
|
+
.sidebar-toggle {
|
|
5033
|
+
position: fixed;
|
|
5034
|
+
/* Same offset from the top frame line as the footer keeps from the bottom. */
|
|
5035
|
+
top: var(--bp-sidebar-chrome-inset-top);
|
|
5036
|
+
left: var(--bp-sidebar-content-inset);
|
|
5037
|
+
z-index: 320;
|
|
5038
|
+
display: inline-flex;
|
|
5039
|
+
align-items: center;
|
|
5040
|
+
justify-content: center;
|
|
5041
|
+
width: var(--bp-sidebar-toggle-size);
|
|
5042
|
+
height: var(--bp-sidebar-toggle-size);
|
|
5043
|
+
padding: 0;
|
|
5044
|
+
border: 1px solid var(--bp-edge);
|
|
5045
|
+
border-radius: var(--bp-radius-6, 6px);
|
|
5046
|
+
background: var(--bp-fill-amb);
|
|
5047
|
+
color: var(--bp-text-secondary);
|
|
5048
|
+
cursor: pointer;
|
|
5049
|
+
transition: none;
|
|
5050
|
+
}
|
|
5051
|
+
.sidebar-toggle:hover {
|
|
5052
|
+
border-color: var(--bp-ink-line);
|
|
5053
|
+
background: var(--bp-paper);
|
|
5054
|
+
color: var(--bp-ink);
|
|
5055
|
+
transition:
|
|
5056
|
+
border-color var(--bp-duration-fast) var(--bp-ease-out),
|
|
5057
|
+
background-color var(--bp-duration-fast) var(--bp-ease-out),
|
|
5058
|
+
color var(--bp-duration-fast) var(--bp-ease-out);
|
|
5059
|
+
}
|
|
5060
|
+
.sidebar-toggle:focus-visible {
|
|
5061
|
+
outline: 2px solid var(--bp-ink);
|
|
5062
|
+
outline-offset: 2px;
|
|
5063
|
+
}
|
|
5064
|
+
.sidebar-toggle__icons {
|
|
5065
|
+
position: relative;
|
|
5066
|
+
display: block;
|
|
5067
|
+
width: 16px;
|
|
5068
|
+
height: 16px;
|
|
5069
|
+
}
|
|
5070
|
+
.sidebar-toggle__icon {
|
|
5071
|
+
position: absolute;
|
|
5072
|
+
inset: 0;
|
|
5073
|
+
display: flex;
|
|
5074
|
+
align-items: center;
|
|
5075
|
+
justify-content: center;
|
|
5076
|
+
pointer-events: none;
|
|
5077
|
+
}
|
|
5078
|
+
.sidebar-toggle__icon--collapse {
|
|
5079
|
+
opacity: 1;
|
|
5080
|
+
}
|
|
5081
|
+
.sidebar-toggle__icon--expand {
|
|
5082
|
+
opacity: 0;
|
|
5083
|
+
}
|
|
5084
|
+
[data-sidebar="collapsed"] .sidebar-toggle__icon--collapse {
|
|
5085
|
+
opacity: 0;
|
|
5086
|
+
}
|
|
5087
|
+
[data-sidebar="collapsed"] .sidebar-toggle__icon--expand {
|
|
5088
|
+
opacity: 1;
|
|
5089
|
+
}
|
|
5090
|
+
.sidebar-toggle svg {
|
|
5091
|
+
display: block;
|
|
5092
|
+
}
|
|
5093
|
+
|
|
5094
|
+
@media (min-width: 861px) {
|
|
5095
|
+
/* Top padding lives on the panel (space-6 aligns the search row with the
|
|
5096
|
+
fixed corner toggle); zero the shell's own top padding so it isn't added
|
|
5097
|
+
on top, which would push the search below the toggle. */
|
|
5098
|
+
.bp-sidebar {
|
|
5099
|
+
display: flex;
|
|
5100
|
+
flex-direction: column;
|
|
5101
|
+
overflow: hidden;
|
|
5102
|
+
padding-top: 0;
|
|
5103
|
+
padding-bottom: 0;
|
|
5104
|
+
padding-inline: 0;
|
|
5105
|
+
}
|
|
5106
|
+
|
|
5107
|
+
/* The panel scrolls; padding-bottom reserves the fixed footer band so every
|
|
5108
|
+
TOC entry can scroll fully into view above it. scroll-padding-bottom keeps
|
|
5109
|
+
browser-managed scroll (keyboard focus, scrollIntoView, find-in-page) from
|
|
5110
|
+
landing a near-bottom entry under the fixed footer overlay. Horizontal
|
|
5111
|
+
padding aligns the TOC pills with the fixed corner toggle. */
|
|
5112
|
+
.bp-sidebar__panel {
|
|
5113
|
+
flex: 1 1 auto;
|
|
5114
|
+
min-height: 0;
|
|
5115
|
+
height: auto;
|
|
5116
|
+
overflow-x: hidden;
|
|
5117
|
+
overflow-y: auto;
|
|
5118
|
+
overscroll-behavior: contain;
|
|
5119
|
+
-webkit-overflow-scrolling: touch;
|
|
5120
|
+
padding-top: var(--bp-space-6);
|
|
5121
|
+
padding-inline: var(--bp-sidebar-content-inset);
|
|
5122
|
+
padding-bottom: var(--bp-sidebar-scroll-padding-bottom);
|
|
5123
|
+
scroll-padding-bottom: var(--bp-sidebar-scroll-padding-bottom);
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
/* Drop the switcher below the header scrim (search stays above it on z 300). */
|
|
5127
|
+
.doc-switcher {
|
|
5128
|
+
margin-top: max(
|
|
5129
|
+
0px,
|
|
5130
|
+
calc(
|
|
5131
|
+
var(--bp-sidebar-header-scrim) - var(--bp-space-6) + var(--bp-space-2) -
|
|
5132
|
+
var(--bp-sidebar-toggle-size) - var(--bp-space-3) + var(--bp-space-2)
|
|
5133
|
+
)
|
|
5134
|
+
);
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5137
|
+
/* The rail slides as one unit. At rest transform is none, so the fixed scrims
|
|
5138
|
+
(::before/::after) + footer stay viewport-anchored for the scroll dissolve;
|
|
5139
|
+
during the slide a non-none transform makes the rail their containing block,
|
|
5140
|
+
so they ride along with it (their %-based geometry resolves against the
|
|
5141
|
+
rail's own width, which is identical math — see the footer rule below).
|
|
5142
|
+
Transition only after bp-sidebar-animate is set (index.html) to skip first
|
|
5143
|
+
paint; visibility flips at the end so the rail leaves the tab order. */
|
|
5144
|
+
html.bp-sidebar-animate .bp-sidebar {
|
|
5145
|
+
transition:
|
|
5146
|
+
transform var(--bp-duration-slow) var(--bp-ease-in-out),
|
|
5147
|
+
visibility var(--bp-duration-slow);
|
|
5148
|
+
}
|
|
5149
|
+
[data-sidebar="collapsed"] .bp-sidebar {
|
|
5150
|
+
transform: translateX(-100%);
|
|
5151
|
+
visibility: hidden;
|
|
5152
|
+
pointer-events: none;
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
/* The sheet reclaims the rail's width in step with the slide. <main> is laid
|
|
5156
|
+
out with margin-inline (blueprint.css shell rules), not `left`, so the
|
|
5157
|
+
reclaim must transition margin — transitioning `left` on a static box was a
|
|
5158
|
+
no-op, which made the sheet snap while the rail slid. Same duration/easing
|
|
5159
|
+
as the rail + frame line so all three move as one. Gated on
|
|
5160
|
+
bp-sidebar-animate so it never fires on first paint. */
|
|
5161
|
+
html.bp-sidebar-animate[data-bp-document] main {
|
|
5162
|
+
transition: margin var(--bp-duration-slow) var(--bp-ease-in-out);
|
|
5163
|
+
}
|
|
5164
|
+
html.bp-sidebar-animate[data-bp-document]::after {
|
|
5165
|
+
transition: background-position var(--bp-duration-slow) var(--bp-ease-in-out);
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5168
|
+
/* Footer pins to the bottom of the rail, spanning the TOC band: left edge on
|
|
5169
|
+
the entry pill (content edge) and right edge at the rail's content edge. */
|
|
5170
|
+
.sidebar-footer {
|
|
5171
|
+
position: fixed;
|
|
5172
|
+
left: var(--bp-sidebar-content-inset);
|
|
5173
|
+
right: calc(100% - var(--bp-sidebar) + var(--bp-sidebar-content-inset));
|
|
5174
|
+
bottom: var(--bp-sidebar-chrome-inset-bottom);
|
|
5175
|
+
z-index: 300;
|
|
5176
|
+
}
|
|
5177
|
+
|
|
5178
|
+
/* Fade scrim above the footer: the TOC scrolls out beneath it (z below the
|
|
5179
|
+
footer controls), with a solid bottom band backing the footer. */
|
|
5180
|
+
.bp-sidebar::after {
|
|
5181
|
+
content: "";
|
|
5182
|
+
position: fixed;
|
|
5183
|
+
left: 0;
|
|
5184
|
+
bottom: 0;
|
|
5185
|
+
width: var(--bp-sidebar);
|
|
5186
|
+
height: calc(var(--bp-sidebar-footer) + var(--bp-space-4));
|
|
5187
|
+
background: var(--bp-sidebar-fade);
|
|
5188
|
+
pointer-events: none;
|
|
5189
|
+
z-index: 200;
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
/* Radial scrim around the corner toggle: the TOC dissolves in a circle that
|
|
5193
|
+
radiates from the icon as entries scroll up beneath it. The box is large
|
|
5194
|
+
enough to hold the gradient's outer radius; transparent beyond it. */
|
|
5195
|
+
.bp-sidebar::before {
|
|
5196
|
+
content: "";
|
|
5197
|
+
position: fixed;
|
|
5198
|
+
top: 0;
|
|
5199
|
+
left: 0;
|
|
5200
|
+
width: var(--bp-sidebar);
|
|
5201
|
+
height: var(--bp-sidebar-header-scrim);
|
|
5202
|
+
background: var(--bp-sidebar-icon-fade);
|
|
5203
|
+
pointer-events: none;
|
|
5204
|
+
z-index: 200;
|
|
5205
|
+
}
|
|
5206
|
+
|
|
5207
|
+
/* Collapsed: the rail (slid out via the rule above) vacates its width and the
|
|
5208
|
+
sheet reclaims it, trailing one space-4 behind the rail's edge throughout. */
|
|
5209
|
+
[data-sidebar="collapsed"][data-bp-document] {
|
|
5210
|
+
--bp-shell-inset-left: var(--bp-space-4);
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5214
|
+
/* Below the rail's fixed breakpoint it becomes a static horizontal nav,
|
|
5215
|
+
where collapsing has no meaning — hide the control entirely. */
|
|
5216
|
+
@media (max-width: 860px) {
|
|
5217
|
+
.sidebar-toggle {
|
|
5218
|
+
display: none;
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
|
|
5222
|
+
@media print {
|
|
5223
|
+
body {
|
|
5224
|
+
padding-top: 0;
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5227
|
+
.site-nav,
|
|
5228
|
+
.bp-sidebar .theme-toggle,
|
|
5229
|
+
.sidebar-toggle {
|
|
5230
|
+
display: none;
|
|
5231
|
+
}
|
|
5232
|
+
}
|
package/dist/blueprint.js
CHANGED
|
@@ -25,6 +25,9 @@ const THEME_BULB_ICON = [
|
|
|
25
25
|
'M10 21H14',
|
|
26
26
|
'M9.00082 15C9.00098 13 8.50098 12.5 7.50082 11.5C6.50067 10.5 6.02422 9.48689 6.00082 8C5.95284 4.95029 8.00067 3 12.0008 3C16.001 3 18.0488 4.95029 18.0008 8C17.9774 9.48689 17.5007 10.5 16.5008 11.5C15.501 12.5 15.001 13 15.0008 15',
|
|
27
27
|
]
|
|
28
|
+
const BLUEPRINT_LOGO_OUTLINE =
|
|
29
|
+
'M7.3 19H12.7C14.3802 19 15.2202 19 15.862 18.673C16.4265 18.3854 16.8854 17.9265 17.173 17.362C17.5 16.7202 17.5 15.8802 17.5 14.2V9.1075C17.5 8.3982 17.5 8.04355 17.4222 7.70852C17.3531 7.41144 17.2392 7.12663 17.0843 6.86389C16.9096 6.56761 16.665 6.31078 16.1759 5.79716L13.0259 2.48966C12.5028 1.94045 12.2413 1.66585 11.9324 1.46921C11.6585 1.29489 11.3582 1.16618 11.0431 1.08809C10.6876 1 10.3084 1 9.55 1H7.3C5.61984 1 4.77976 1 4.13803 1.32698C3.57354 1.6146 3.1146 2.07354 2.82698 2.63803C2.5 3.27976 2.5 4.11984 2.5 5.8V14.2C2.5 15.8802 2.5 16.7202 2.82698 17.362C3.1146 17.9265 3.57354 18.3854 4.13803 18.673C4.77976 19 5.61984 19 7.3 19Z'
|
|
30
|
+
const BLUEPRINT_LOGO_FILL = 'M16 16H5V6L16 16ZM6.5 14.5H11.5L6.5 9.5V14.5Z'
|
|
28
31
|
|
|
29
32
|
function metaSelector({ name, property } = {}) {
|
|
30
33
|
if (property) return `meta[property="${property}"]`
|
|
@@ -103,6 +106,18 @@ function createChromeSvg(doc, pathD, size = 16) {
|
|
|
103
106
|
return svg
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
function createBlueprintLogo(doc) {
|
|
110
|
+
const svg = createChromeSvg(doc, BLUEPRINT_LOGO_OUTLINE, 20)
|
|
111
|
+
svg.setAttribute('viewBox', '0 0 20 20')
|
|
112
|
+
svg.classList.add('site-nav__logo')
|
|
113
|
+
const fill = doc.createElementNS('http://www.w3.org/2000/svg', 'path')
|
|
114
|
+
fill.setAttribute('d', BLUEPRINT_LOGO_FILL)
|
|
115
|
+
fill.setAttribute('fill', 'currentColor')
|
|
116
|
+
fill.setAttribute('stroke', 'none')
|
|
117
|
+
svg.append(fill)
|
|
118
|
+
return svg
|
|
119
|
+
}
|
|
120
|
+
|
|
106
121
|
function injectScrollProgress(doc) {
|
|
107
122
|
if (doc.querySelector('.scroll-progress')) return null
|
|
108
123
|
const bar = doc.createElement('div')
|
|
@@ -131,9 +146,7 @@ function injectSiteNav(doc, win) {
|
|
|
131
146
|
brand.href = brandHref
|
|
132
147
|
brand.setAttribute('aria-label', `${brandLabel} home`)
|
|
133
148
|
|
|
134
|
-
const logo = doc
|
|
135
|
-
logo.className = 'site-nav__logo'
|
|
136
|
-
logo.setAttribute('aria-hidden', 'true')
|
|
149
|
+
const logo = createBlueprintLogo(doc)
|
|
137
150
|
|
|
138
151
|
const wordmark = doc.createElement('span')
|
|
139
152
|
wordmark.className = 'site-nav__wordmark'
|
|
@@ -905,15 +918,16 @@ function wireSearchPalette(doc, win) {
|
|
|
905
918
|
})
|
|
906
919
|
}
|
|
907
920
|
|
|
908
|
-
function injectDocumentChrome(doc, win = doc.defaultView) {
|
|
921
|
+
function injectDocumentChrome(doc, win = doc.defaultView, navigationTree = []) {
|
|
909
922
|
if (doc.body?.dataset.bpChromeReady === 'true') return
|
|
910
923
|
const main = doc.querySelector('main')
|
|
911
924
|
if (!main) return
|
|
912
925
|
|
|
913
|
-
const
|
|
926
|
+
const hasNavigation = navigationTree.length > 0
|
|
927
|
+
const sidebar = hasNavigation ? injectSidebar(doc, win) : null
|
|
914
928
|
const chrome = [
|
|
915
929
|
injectSiteNav(doc, win),
|
|
916
|
-
injectScrollProgress(doc),
|
|
930
|
+
hasNavigation ? injectScrollProgress(doc) : null,
|
|
917
931
|
sidebar ? injectSidebarToggle(doc) : null,
|
|
918
932
|
sidebar,
|
|
919
933
|
].filter(Boolean)
|
|
@@ -944,8 +958,9 @@ export function initializeBlueprintRuntime(doc = document, win = window) {
|
|
|
944
958
|
// Resolve the theme before chrome is injected so the toggle renders in the
|
|
945
959
|
// correct state and the page doesn't repaint a second time.
|
|
946
960
|
resolveInitialTheme(doc, win)
|
|
947
|
-
|
|
948
|
-
|
|
961
|
+
const navigationTree = ensureDocumentNavTree(doc)
|
|
962
|
+
injectDocumentChrome(doc, win, navigationTree)
|
|
963
|
+
generateNavigationFromHeadings(doc, navigationTree)
|
|
949
964
|
wireSectionHeadingLinks(doc, win)
|
|
950
965
|
wireSearchPalette(doc, win)
|
|
951
966
|
wireFigureDiagramSize(doc)
|
|
@@ -1507,8 +1522,7 @@ function populateAllBpToc(doc, tree) {
|
|
|
1507
1522
|
}
|
|
1508
1523
|
}
|
|
1509
1524
|
|
|
1510
|
-
function generateNavigationFromHeadings(doc) {
|
|
1511
|
-
const tree = ensureDocumentNavTree(doc)
|
|
1525
|
+
function generateNavigationFromHeadings(doc, tree = ensureDocumentNavTree(doc)) {
|
|
1512
1526
|
if (tree.length === 0) return
|
|
1513
1527
|
|
|
1514
1528
|
const navFragment = renderNavTree(doc, tree)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obvi/blueprint",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A classless-first CSS design system for beautiful technical blueprint documents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,10 +39,12 @@
|
|
|
39
39
|
"build": "npm run build:code && node scripts/build-package.mjs",
|
|
40
40
|
"build:code": "node code-highlighting/scripts/build.mjs",
|
|
41
41
|
"render": "node harness/render.mjs",
|
|
42
|
-
"measure": "node harness/measure.mjs && node harness/behavior.mjs",
|
|
42
|
+
"measure": "node harness/measure.mjs && node harness/behavior.mjs && node harness/navigation-clipping.mjs",
|
|
43
43
|
"verify:package": "node scripts/verify-package.mjs",
|
|
44
|
+
"verify:release-evidence": "node --test harness/release-evidence.test.mjs",
|
|
44
45
|
"lint:style": "node scripts/lint-style.mjs",
|
|
45
|
-
"
|
|
46
|
+
"release:evidence": "node harness/release-evidence.mjs",
|
|
47
|
+
"verify": "npm run build && npm run lint:style && npm run verify:release-evidence && npm run measure && npm run verify:package",
|
|
46
48
|
"prepack": "npm run build"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|