@bongos/core 1.19.92 → 1.19.94
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/.bongos-core.json +38 -88
- package/docs/design/mocks/projects-hub-mock.html +991 -0
- package/docs/design/mocks/projects-hub-variants.html +146 -0
- package/docs/design/projects-hub-direction.md +347 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +240 -0
- package/public-landing/index.html +10 -4
- package/public-landing/projects.html +663 -44
- package/scripts/gds/run-unit-tests.js +0 -1
- package/src/bongos/module-scope-map.js +1 -1
- package/src/module-api.js +5 -1
- package/src/platform-server.js +24 -42
- package/tests/known_failing_ratchet.mjs +7 -8
- package/tests/landing_page.mjs +15 -2
- package/tests/projects_hub.mjs +205 -0
- package/public-landing/gate/README.md +0 -137
- package/public-landing/gate/dispmaps/bar.png +0 -0
- package/public-landing/gate/dispmaps/flat.png +0 -0
- package/public-landing/gate/dispmaps/key.png +0 -0
- package/public-landing/gate/index.html +0 -634
- package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
- package/public-landing/gate/previews/off-the-boats.jpg +0 -0
- package/public-landing/gate/space.css +0 -809
- package/public-landing/gate/space.js +0 -1848
- package/public-landing/gate/sprites/enter.png +0 -0
- package/public-landing/gate/sprites/search.png +0 -0
- package/public-landing/gate/sprites/terminal-blank.png +0 -0
- package/public-landing/gate/sprites/tile.png +0 -0
- package/tests/gate_launch_metadata.mjs +0 -67
|
@@ -293,6 +293,12 @@ h1{font-family:var(--font-display);font-weight:700;font-size:clamp(24px,3.4vw,32
|
|
|
293
293
|
.confirmRow{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:13.5px;font-weight:700;color:var(--err);}
|
|
294
294
|
.confirmRow .plain{font-weight:600;color:var(--ink-soft);}
|
|
295
295
|
.finePrint{font-size:12.5px;color:var(--ink-faint);font-weight:600;margin-top:16px;line-height:1.5;}
|
|
296
|
+
/* a link in fine print still has to look like one — without this it fell back to
|
|
297
|
+
the browser's default blue, the one colour in the page that is not ours */
|
|
298
|
+
.finePrint a{color:var(--accent-deep);font-weight:700;text-decoration:none;}
|
|
299
|
+
.finePrint a:hover{text-decoration:underline;}
|
|
300
|
+
:root[data-mode="dark"] .finePrint a{color:var(--sun-lit);}
|
|
301
|
+
@media (prefers-color-scheme: dark){:root:not([data-mode="light"]) .finePrint a{color:var(--sun-lit);}}
|
|
296
302
|
|
|
297
303
|
/* ── progress strips: one visual, per-flow vocabularies (direction §4).
|
|
298
304
|
Default = the sea-blue "working" look; .isLive = verified-landed green;
|
|
@@ -359,6 +365,240 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
359
365
|
.foot{position:relative;z-index:1;text-align:center;font-size:13px;font-weight:600;color:var(--foot-ink);padding:0 20px 34px;text-shadow:var(--chrome-glow);}
|
|
360
366
|
.foot code{font-family:var(--font-mono);color:var(--foot-code);}
|
|
361
367
|
|
|
368
|
+
/* ═══════════════════════════════════════════════════════════════════════
|
|
369
|
+
THE PROJECT HUB (task 1002743, direction docs/design/projects-hub-direction.md §6)
|
|
370
|
+
|
|
371
|
+
/projects is one surface holding three views — map (sky | table), your
|
|
372
|
+
projects, new project — under a persistent shell. These components were
|
|
373
|
+
authored page-locally in the approved mock and GRADUATE here, so the hub
|
|
374
|
+
and the manage cards are one system rather than two that resemble each
|
|
375
|
+
other. Every dark override is written TWICE (the explicit hook and the
|
|
376
|
+
prefers-color-scheme twin); tests/cosmos_token_contrast.mjs compares them.
|
|
377
|
+
═══════════════════════════════════════════════════════════════════════ */
|
|
378
|
+
|
|
379
|
+
/* ── the persistent shell header. It belongs to the PAGE, not to the orb
|
|
380
|
+
field, so it never drifts, scales or pans with the sky — and it renders in
|
|
381
|
+
EVERY view including the table, which is what the gate's
|
|
382
|
+
`body:not(.view-space)` rule got wrong. ── */
|
|
383
|
+
.shell{position:relative;z-index:40;display:flex;align-items:center;justify-content:space-between;
|
|
384
|
+
gap:clamp(10px,2vw,22px);padding:16px clamp(16px,4vw,34px);flex-wrap:wrap;}
|
|
385
|
+
/* The wordmark TYPES, so its width changes every ~190ms. As the middle child of
|
|
386
|
+
a space-between row the nav would slide with it. Equal `flex:1 1 0` flanks
|
|
387
|
+
compute to the same width regardless of content, which pins the nav dead
|
|
388
|
+
centre and lets the mark change width inside its own box. Measured: 0px drift
|
|
389
|
+
across seven typed states. */
|
|
390
|
+
.shell .mark{flex:1 1 0;min-width:0;
|
|
391
|
+
font-family:var(--font-display);font-weight:700;font-size:22px;letter-spacing:-0.5px;
|
|
392
|
+
text-transform:lowercase;color:var(--chrome-ink);text-decoration:none;text-shadow:var(--chrome-glow);
|
|
393
|
+
white-space:nowrap;}
|
|
394
|
+
.shell .mark span{color:var(--chrome-accent);}
|
|
395
|
+
.shell .caret{color:var(--chrome-accent);animation:cbBlink 1s step-end infinite;font-weight:400;}
|
|
396
|
+
@keyframes cbBlink{50%{opacity:0;}}
|
|
397
|
+
.shell-right{flex:1 1 0;display:flex;align-items:center;justify-content:flex-end;
|
|
398
|
+
gap:clamp(8px,1.6vw,14px);flex-wrap:wrap;}
|
|
399
|
+
|
|
400
|
+
/* the three destinations, in the .btn-space glass voice so the nav and the
|
|
401
|
+
chrome buttons read as one family */
|
|
402
|
+
.navrow{flex:0 0 auto;display:flex;align-items:center;gap:4px;background:var(--glass-bg);
|
|
403
|
+
border:1.5px solid var(--glass-border);border-radius:999px;padding:4px;backdrop-filter:blur(4px);}
|
|
404
|
+
.navrow a{font-family:var(--font-display);font-weight:700;font-size:13.5px;color:var(--glass-ink);
|
|
405
|
+
text-decoration:none;padding:7px 15px;border-radius:999px;white-space:nowrap;text-shadow:var(--chrome-glow);}
|
|
406
|
+
.navrow a:hover{color:var(--chrome-accent);}
|
|
407
|
+
.navrow a[aria-current="page"]{background:var(--accent);color:#fff;text-shadow:none;}
|
|
408
|
+
/* Home LEAVES the hub, so it never takes the current-view pill and a hairline
|
|
409
|
+
sets it apart. Without it the header offered no way back to the landing page
|
|
410
|
+
except the wordmark, which nothing announced as a link. */
|
|
411
|
+
.navrow .navHome{color:var(--glass-ink);opacity:.9;}
|
|
412
|
+
.navrow .navHome:hover{opacity:1;color:var(--chrome-accent);}
|
|
413
|
+
.navSep{width:1px;align-self:stretch;margin:4px 3px;background:var(--glass-border);flex:0 0 auto;}
|
|
414
|
+
/* below the mid breakpoint the three groups stack and centre rather than
|
|
415
|
+
fighting for one row — equal flanks only buy a centred nav while it fits */
|
|
416
|
+
@media (max-width:820px){
|
|
417
|
+
.shell{justify-content:center;}
|
|
418
|
+
.shell .mark,.shell-right{flex:0 0 auto;}
|
|
419
|
+
.navrow{order:3;flex-basis:100%;justify-content:center;}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* ── the per-view control strip: Sky|Table, the only-mine filter, search ── */
|
|
423
|
+
.toolbar{position:relative;z-index:30;display:flex;align-items:center;gap:12px;flex-wrap:wrap;
|
|
424
|
+
padding:0 clamp(16px,4vw,34px) 16px;}
|
|
425
|
+
.seg{display:inline-flex;background:var(--glass-bg);border:1.5px solid var(--glass-border);
|
|
426
|
+
border-radius:999px;padding:4px;gap:4px;backdrop-filter:blur(4px);}
|
|
427
|
+
.seg button{font-family:var(--font-display);font-weight:700;font-size:13px;color:var(--glass-ink);
|
|
428
|
+
background:none;border:none;cursor:pointer;padding:7px 16px;border-radius:999px;text-shadow:var(--chrome-glow);}
|
|
429
|
+
.seg button[aria-pressed="true"]{background:var(--accent);color:#fff;text-shadow:none;}
|
|
430
|
+
|
|
431
|
+
.search{flex:1;min-width:190px;max-width:460px;display:flex;align-items:center;gap:9px;
|
|
432
|
+
background:var(--glass-bg);border:1.5px solid var(--glass-border);border-radius:999px;
|
|
433
|
+
padding:8px 16px;backdrop-filter:blur(4px);}
|
|
434
|
+
.search svg{width:15px;height:15px;fill:none;stroke:var(--glass-ink);stroke-width:2;flex:0 0 auto;}
|
|
435
|
+
.search input{flex:1;min-width:0;background:none;border:none;outline:none;font-family:var(--font-mono);
|
|
436
|
+
font-size:13px;color:var(--chrome-ink);}
|
|
437
|
+
.search input::placeholder{color:var(--glass-ink);opacity:.75;}
|
|
438
|
+
.search .count{font-family:var(--font-mono);font-size:11.5px;color:var(--glass-ink);white-space:nowrap;}
|
|
439
|
+
|
|
440
|
+
/* the only-mine filter — a real two-state control, not a link: it filters the
|
|
441
|
+
SAME data in place rather than navigating */
|
|
442
|
+
.switch{display:inline-flex;align-items:center;gap:9px;cursor:pointer;background:var(--glass-bg);
|
|
443
|
+
border:1.5px solid var(--glass-border);border-radius:999px;padding:7px 15px 7px 11px;
|
|
444
|
+
font-family:var(--font-display);font-weight:700;font-size:13px;color:var(--glass-ink);
|
|
445
|
+
backdrop-filter:blur(4px);text-shadow:var(--chrome-glow);white-space:nowrap;}
|
|
446
|
+
.switch input{position:absolute;opacity:0;pointer-events:none;}
|
|
447
|
+
.switch .track{width:32px;height:18px;border-radius:999px;background:color-mix(in srgb,var(--ink) 22%,transparent);
|
|
448
|
+
position:relative;flex:0 0 auto;transition:background .16s ease;}
|
|
449
|
+
.switch .track::after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
|
|
450
|
+
background:#fff;transition:transform .16s ease;box-shadow:0 1px 3px rgba(0,0,0,.3);}
|
|
451
|
+
.switch input:checked + .track{background:var(--accent);}
|
|
452
|
+
.switch input:checked + .track::after{transform:translateX(14px);}
|
|
453
|
+
.switch input:focus-visible + .track{outline:2px solid var(--accent);outline-offset:2px;}
|
|
454
|
+
.switch:has(input:checked){color:var(--chrome-accent);border-color:var(--accent);}
|
|
455
|
+
|
|
456
|
+
/* ── the orb field. Ground B (owner, 2026-08-13): dual-mode full-bleed — the
|
|
457
|
+
sky IS the page and follows light/dark, which is what --sky-bg/--star-display
|
|
458
|
+
already encode. Deliberately STATIC: where things sit is this pass, how the
|
|
459
|
+
sky moves is not. Component-scoped custom properties only — a page-local or
|
|
460
|
+
component `:root` would outrank the branding pack spliced after this file. ── */
|
|
461
|
+
.fieldWrap{padding:0;max-width:none;}
|
|
462
|
+
.field{position:relative;height:calc(100vh - 232px);min-height:340px;overflow:hidden;
|
|
463
|
+
--grid-ink:color-mix(in srgb,var(--rule) 92%,transparent);}
|
|
464
|
+
.field .ground{position:absolute;inset:0;background:var(--sky-bg);}
|
|
465
|
+
/* the gravity sheet, flattened: two gradients under a radial mask */
|
|
466
|
+
.field .ground::after{content:"";position:absolute;inset:-10%;
|
|
467
|
+
background-image:linear-gradient(var(--grid-ink) 1px,transparent 1px),
|
|
468
|
+
linear-gradient(90deg,var(--grid-ink) 1px,transparent 1px);
|
|
469
|
+
background-size:74px 74px;
|
|
470
|
+
-webkit-mask-image:radial-gradient(ellipse 62% 58% at 50% 50%,#000 12%,transparent 78%);
|
|
471
|
+
mask-image:radial-gradient(ellipse 62% 58% at 50% 50%,#000 12%,transparent 78%);}
|
|
472
|
+
.field .stars{position:absolute;inset:0;display:var(--star-display);}
|
|
473
|
+
.field .stars i{position:absolute;width:2px;height:2px;border-radius:50%;background:#fff;opacity:.55;}
|
|
474
|
+
|
|
475
|
+
/* a project rendered as an orb. Colour comes from tokens, never a literal, so
|
|
476
|
+
every accent follows a re-theme. */
|
|
477
|
+
.worldOrb{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;
|
|
478
|
+
align-items:center;gap:9px;cursor:pointer;background:none;border:none;padding:0;text-decoration:none;}
|
|
479
|
+
.worldOrb .disc{width:var(--d,96px);height:var(--d,96px);border-radius:50%;position:relative;
|
|
480
|
+
background:radial-gradient(circle at 34% 28%,color-mix(in srgb,var(--acc) 22%,#fff) 0%,
|
|
481
|
+
var(--acc) 42%,color-mix(in srgb,var(--acc) 72%,#000) 100%);
|
|
482
|
+
box-shadow:0 0 34px -4px color-mix(in srgb,var(--acc) 55%,transparent);}
|
|
483
|
+
.worldOrb .disc::after{content:"";position:absolute;inset:0;border-radius:50%;
|
|
484
|
+
border:1.5px solid color-mix(in srgb,#fff 55%,transparent);}
|
|
485
|
+
.worldOrb .nm{font-family:var(--font-display);font-weight:700;font-size:14.5px;color:var(--chrome-ink);
|
|
486
|
+
text-shadow:var(--chrome-glow);white-space:nowrap;}
|
|
487
|
+
.worldOrb .sub{font-family:var(--font-mono);font-size:11.5px;color:var(--chrome-accent);
|
|
488
|
+
text-shadow:var(--chrome-glow);white-space:nowrap;}
|
|
489
|
+
/* z-index 3: the disc's ::after rim is positioned and later in DOM order, so
|
|
490
|
+
without this the badge is painted over by the orb it labels */
|
|
491
|
+
.worldOrb .badge{position:absolute;top:-11px;left:50%;transform:translateX(-50%);z-index:3;
|
|
492
|
+
font-family:var(--font-mono);font-size:10.5px;font-weight:500;padding:2px 9px;border-radius:999px;
|
|
493
|
+
white-space:nowrap;background:var(--gold-soft);color:#2b170e;
|
|
494
|
+
box-shadow:0 2px 8px color-mix(in srgb,var(--ink) 30%,transparent);}
|
|
495
|
+
/* the owned ring is ONE ring, not two — a second cream halo read as a defect */
|
|
496
|
+
.worldOrb[data-rel="owner"] .disc{box-shadow:0 0 34px -4px color-mix(in srgb,var(--acc) 55%,transparent),
|
|
497
|
+
0 0 0 3px var(--accent);}
|
|
498
|
+
.worldOrb[data-rel="owner"] .badge{background:var(--accent);color:#fff;}
|
|
499
|
+
/* building, not owning — a lighter claim, said differently on purpose */
|
|
500
|
+
.worldOrb[data-rel="builder"] .disc{box-shadow:0 0 34px -4px color-mix(in srgb,var(--acc) 55%,transparent),
|
|
501
|
+
0 0 0 2px color-mix(in srgb,var(--accent) 55%,transparent);}
|
|
502
|
+
.worldOrb[data-rel="builder"] .badge{background:var(--accent-soft);color:var(--accent-deep);
|
|
503
|
+
border:1px solid color-mix(in srgb,var(--accent) 45%,transparent);}
|
|
504
|
+
:root[data-mode="dark"] .worldOrb[data-rel="builder"] .badge{color:var(--sun-lit);}
|
|
505
|
+
@media (prefers-color-scheme: dark){:root:not([data-mode="light"]) .worldOrb[data-rel="builder"] .badge{color:var(--sun-lit);}}
|
|
506
|
+
.worldOrb .chip{transform:scale(.88);}
|
|
507
|
+
|
|
508
|
+
/* what the only-mine filter shows when you are connected to nothing. Not a
|
|
509
|
+
hypothetical — GET /my-projects returns { owned: [], building: [] } for the
|
|
510
|
+
platform's own archon today, so it is the DAY-ONE state. */
|
|
511
|
+
.fieldEmpty{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;
|
|
512
|
+
justify-content:center;gap:14px;text-align:center;padding:26px;z-index:4;}
|
|
513
|
+
.field.isEmpty .fieldEmpty{display:flex;}
|
|
514
|
+
.fieldEmpty p{font-size:15px;font-weight:600;color:var(--chrome-ink);max-width:44ch;line-height:1.55;
|
|
515
|
+
text-shadow:var(--chrome-glow);}
|
|
516
|
+
.fieldEmpty .sub{font-size:13.5px;font-weight:600;color:var(--glass-ink);}
|
|
517
|
+
|
|
518
|
+
.hintbar{position:absolute;left:50%;bottom:14px;transform:translateX(-50%);display:flex;gap:8px;
|
|
519
|
+
flex-wrap:wrap;justify-content:center;font-family:var(--font-mono);font-size:11.5px;
|
|
520
|
+
color:var(--glass-ink);background:var(--glass-bg);border:1.5px solid var(--glass-border);
|
|
521
|
+
border-radius:999px;padding:7px 16px;backdrop-filter:blur(4px);max-width:92%;}
|
|
522
|
+
.hintbar kbd{font-family:var(--font-mono);background:color-mix(in srgb,var(--accent) 24%,transparent);
|
|
523
|
+
border-radius:5px;padding:1px 6px;font-size:11px;color:var(--chrome-accent);}
|
|
524
|
+
|
|
525
|
+
/* ── the directory table: the same rows, the same filter, and the shell is
|
|
526
|
+
still overhead — the gate's table dropped the projects door entirely. ── */
|
|
527
|
+
.tbl{width:100%;border-collapse:collapse;background:var(--card);border-radius:20px;overflow:hidden;
|
|
528
|
+
box-shadow:var(--shadow-card);}
|
|
529
|
+
.tbl th{text-align:left;font-family:var(--font-display);font-size:11.5px;font-weight:700;
|
|
530
|
+
letter-spacing:.9px;text-transform:uppercase;color:var(--ink-faint);padding:15px 18px;
|
|
531
|
+
border-bottom:2px solid var(--line);white-space:nowrap;}
|
|
532
|
+
.tbl td{padding:15px 18px;border-bottom:1px solid var(--line);font-size:14px;font-weight:600;
|
|
533
|
+
color:var(--ink-soft);vertical-align:middle;}
|
|
534
|
+
.tbl tr:last-child td{border-bottom:none;}
|
|
535
|
+
.tbl tr:hover td{background:color-mix(in srgb,var(--accent) 5%,transparent);}
|
|
536
|
+
.tbl .pname{display:flex;align-items:center;gap:11px;}
|
|
537
|
+
.tbl .pdot{width:11px;height:11px;border-radius:50%;background:var(--acc);flex:0 0 auto;
|
|
538
|
+
box-shadow:0 0 0 3px color-mix(in srgb,var(--acc) 22%,transparent);}
|
|
539
|
+
.tbl .ptitle{font-family:var(--font-display);font-weight:700;font-size:15px;color:var(--ink);}
|
|
540
|
+
.tbl .ptag{font-size:12.5px;color:var(--ink-faint);font-weight:600;}
|
|
541
|
+
.tbl .num{font-family:var(--font-mono);font-weight:500;}
|
|
542
|
+
.tbl .pill{display:inline-block;font-family:var(--font-mono);font-size:11px;padding:3px 10px;
|
|
543
|
+
border-radius:999px;background:var(--accent-soft);color:var(--accent-deep);}
|
|
544
|
+
:root[data-mode="dark"] .tbl .pill{color:var(--sun-lit);}
|
|
545
|
+
@media (prefers-color-scheme: dark){:root:not([data-mode="light"]) .tbl .pill{color:var(--sun-lit);}}
|
|
546
|
+
.tbl .tags{display:flex;gap:6px;flex-wrap:wrap;}
|
|
547
|
+
.tbl .tags span{font-family:var(--font-mono);font-size:10.5px;padding:2px 8px;border-radius:999px;
|
|
548
|
+
border:1px solid var(--line);color:var(--ink-faint);}
|
|
549
|
+
.tbl td.right{text-align:right;}
|
|
550
|
+
/* Nothing in an action or status cell may wrap: "Manage →" breaking after the
|
|
551
|
+
space and the chip's recency dropping to a second line both read as a broken
|
|
552
|
+
control rather than a tight column. Those two cells claim their width and the
|
|
553
|
+
name cell absorbs the slack. */
|
|
554
|
+
.tbl .btn,.tbl .chip{white-space:nowrap;}
|
|
555
|
+
.tbl .colStatus{width:1%;white-space:nowrap;}
|
|
556
|
+
.tbl .colGo{width:1%;white-space:nowrap;padding-left:8px;}
|
|
557
|
+
.tbl .colName{width:auto;}
|
|
558
|
+
@media (max-width:780px){.tbl .colTags,.tbl .colType{display:none;}}
|
|
559
|
+
|
|
560
|
+
/* ── your projects: search + a state filter whose pills carry live counts, so
|
|
561
|
+
a state with nothing in it reads 0 before you click it. The filter GROUPS
|
|
562
|
+
the chip outcomes; it never relabels them — "Live" holds both `live` and
|
|
563
|
+
`live?` because neither is known-down, and each card still says which. ── */
|
|
564
|
+
.mineBar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:20px;}
|
|
565
|
+
.mineBar .search{background:var(--bg-card);border-color:var(--line);}
|
|
566
|
+
.mineBar .search input{color:var(--ink);}
|
|
567
|
+
.mineBar .search input::placeholder{color:var(--ink-faint);}
|
|
568
|
+
.mineBar .search svg{stroke:var(--ink-faint);}
|
|
569
|
+
.mineBar .search .count{color:var(--ink-faint);}
|
|
570
|
+
.filters{display:flex;gap:7px;flex-wrap:wrap;}
|
|
571
|
+
.filters button{display:inline-flex;align-items:center;gap:7px;cursor:pointer;font-family:var(--font-display);
|
|
572
|
+
font-weight:700;font-size:13px;color:var(--ink-soft);background:var(--bg-card);
|
|
573
|
+
border:2px solid var(--line);border-radius:999px;padding:7px 14px;white-space:nowrap;}
|
|
574
|
+
.filters button:hover{border-color:var(--accent);color:var(--accent-deep);}
|
|
575
|
+
.filters button[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:#fff;}
|
|
576
|
+
.filters .n{font-family:var(--font-mono);font-weight:500;font-size:11.5px;opacity:.75;}
|
|
577
|
+
.mineEmpty{font-size:14.5px;font-weight:600;color:var(--ink-soft);padding:10px 0 22px;}
|
|
578
|
+
|
|
579
|
+
/* ── the create rail ── */
|
|
580
|
+
.steps{display:flex;align-items:flex-start;margin-bottom:26px;}
|
|
581
|
+
.steps li{flex:1;display:flex;flex-direction:column;align-items:center;gap:8px;position:relative;list-style:none;}
|
|
582
|
+
.steps li:not(:first-child)::before{content:"";position:absolute;top:15px;right:50%;width:100%;height:3px;
|
|
583
|
+
background:var(--line);margin-right:15px;border-radius:2px;}
|
|
584
|
+
.steps li.done:not(:first-child)::before{background:var(--accent);}
|
|
585
|
+
.steps .snum{width:32px;height:32px;border-radius:50%;border:3px solid var(--line);background:var(--bg-card);
|
|
586
|
+
display:flex;align-items:center;justify-content:center;font-family:var(--font-display);font-weight:700;
|
|
587
|
+
font-size:14px;color:var(--ink-faint);position:relative;z-index:1;}
|
|
588
|
+
.steps li.done .snum{border-color:var(--accent);background:var(--accent);color:#fff;}
|
|
589
|
+
.steps li.now .snum{border-color:var(--accent);color:var(--accent-deep);}
|
|
590
|
+
:root[data-mode="dark"] .steps li.now .snum{color:var(--sun-lit);}
|
|
591
|
+
@media (prefers-color-scheme: dark){:root:not([data-mode="light"]) .steps li.now .snum{color:var(--sun-lit);}}
|
|
592
|
+
.steps .slbl{font-size:12.5px;font-weight:700;color:var(--ink-faint);}
|
|
593
|
+
.steps li.now .slbl,.steps li.done .slbl{color:var(--ink-soft);}
|
|
594
|
+
.createSplit{display:grid;grid-template-columns:1fr 300px;gap:var(--pad-md);align-items:center;}
|
|
595
|
+
@media (max-width:840px){.createSplit{grid-template-columns:1fr;}.createSplit .bongoCell{display:none;}}
|
|
596
|
+
|
|
597
|
+
/* ── the honest empty / not-available-here state. A child instance does not
|
|
598
|
+
provision, so it must SAY so rather than fall into the generic alert. ── */
|
|
599
|
+
.fieldNote{border:2px dashed var(--line);border-radius:18px;padding:24px;text-align:center;
|
|
600
|
+
font-size:14.5px;font-weight:600;color:var(--ink-soft);line-height:1.55;}
|
|
601
|
+
|
|
362
602
|
@media (max-width:640px){
|
|
363
603
|
.projHead{gap:8px;}
|
|
364
604
|
.actions{flex-direction:column;align-items:stretch;}
|
|
@@ -233,7 +233,7 @@ h1.big{font-family:var(--font-display);font-weight:700;font-size:clamp(33px,5.4v
|
|
|
233
233
|
<div class="wrap">
|
|
234
234
|
<section class="lead">
|
|
235
235
|
<div>
|
|
236
|
-
<p class="eyebrow">type <span class="typed" id="typed">
|
|
236
|
+
<p class="eyebrow">type <b>bongos</b> in <span class="typed" id="typed">any AI</span><span class="caret" aria-hidden="true">▌</span></p>
|
|
237
237
|
<h1 class="big" data-brand-tagline>Building software is laying down a beat.</h1>
|
|
238
238
|
<p class="lede">AI agents claim tasks and ship the code; you set the direction and the gates.</p>
|
|
239
239
|
<div class="cta-row">
|
|
@@ -556,9 +556,15 @@ h1.big{font-family:var(--font-display);font-weight:700;font-size:clamp(33px,5.4v
|
|
|
556
556
|
var reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
557
557
|
|
|
558
558
|
/* ── the self-typing eyebrow. DECORATION over real text: the markup already
|
|
559
|
-
|
|
559
|
+
reads "type bongos in any AI" and stays correct with scripting off or
|
|
560
|
+
motion reduced.
|
|
561
|
+
|
|
562
|
+
`bongos` is the CONSTANT and the ASSISTANT is what varies — that is the
|
|
563
|
+
whole claim: one word, and it works in any of them. Typing the first slot
|
|
564
|
+
instead produced "type claude in any AI", which nobody does and which
|
|
565
|
+
inverted the point of the line. ── */
|
|
560
566
|
if (!reduce) {
|
|
561
|
-
var el = document.getElementById('typed'), words = ['
|
|
567
|
+
var el = document.getElementById('typed'), words = ['Claude','ChatGPT','Gemini','any AI'], w = 0;
|
|
562
568
|
(function cycle(){
|
|
563
569
|
var word = words[w++ % words.length], i = 0;
|
|
564
570
|
el.textContent = '';
|
|
@@ -567,7 +573,7 @@ h1.big{font-family:var(--font-display);font-weight:700;font-size:clamp(33px,5.4v
|
|
|
567
573
|
else setTimeout(function erase(){
|
|
568
574
|
if (el.textContent.length) { el.textContent = el.textContent.slice(0, -1); setTimeout(erase, 38); }
|
|
569
575
|
else cycle();
|
|
570
|
-
}, word === '
|
|
576
|
+
}, word === 'any AI' ? 2100 : 900);
|
|
571
577
|
})();
|
|
572
578
|
})();
|
|
573
579
|
}
|