@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 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
 
@@ -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.createElement('span')
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 sidebar = injectSidebar(doc, win)
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
- injectDocumentChrome(doc, win)
948
- generateNavigationFromHeadings(doc)
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.0",
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
- "verify": "npm run build && npm run lint:style && npm run measure && npm run verify:package",
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": {