| 434 |
434 |
|
|
| 435 |
435 |
|
/* 6. Header (desktop UI only)
|
| 436 |
436 |
|
The top app header is part of desktop layout. Mobile UI hides it
|
| 437 |
|
- |
wholesale; rules below are scoped so they cannot leak in. */
|
|
437 |
+ |
wholesale; rules below are scoped so they cannot leak in.
|
|
438 |
+ |
|
|
439 |
+ |
The header is the tab strip, not a band of its own. It sits on the page
|
|
440 |
+ |
ground in --surface-sunken, carries the line that the pane hangs from
|
|
441 |
+ |
(border-bottom), and shares the pane's width so the two read as one
|
|
442 |
+ |
object. §7 has the tab semantic; §8 has the pane. */
|
| 438 |
443 |
|
.ui-mode-desktop .app-header {
|
| 439 |
|
- |
background: var(--surface-raised);
|
|
444 |
+ |
width: 100%;
|
|
445 |
+ |
max-width: var(--width-container);
|
|
446 |
+ |
margin: 0 auto;
|
|
447 |
+ |
background: var(--surface-sunken);
|
| 440 |
448 |
|
border-bottom: var(--border-width) solid var(--border);
|
| 441 |
|
- |
padding: var(--gap-group) var(--gap-pane);
|
|
449 |
+ |
padding: var(--gap-peer) var(--gap-peer) 0;
|
| 442 |
450 |
|
display: flex;
|
| 443 |
|
- |
justify-content: space-between;
|
| 444 |
|
- |
align-items: center;
|
|
451 |
+ |
align-items: flex-end;
|
|
452 |
+ |
gap: var(--gap-section);
|
| 445 |
453 |
|
}
|
| 446 |
454 |
|
|
| 447 |
455 |
|
.ui-mode-desktop .header-content {
|
| 450 |
458 |
|
gap: var(--gap-bound);
|
| 451 |
459 |
|
}
|
| 452 |
460 |
|
|
|
461 |
+ |
/* Utility controls ride the strip. Lifted off the line by the same step the
|
|
462 |
+ |
tabs stand on, so they do not read as sitting in the pane. */
|
| 453 |
463 |
|
.ui-mode-desktop .header-actions {
|
| 454 |
464 |
|
display: flex;
|
| 455 |
465 |
|
align-items: center;
|
| 456 |
466 |
|
gap: var(--gap-peer);
|
| 457 |
|
- |
}
|
| 458 |
|
- |
|
| 459 |
|
- |
.ui-mode-desktop .app-title {
|
| 460 |
|
- |
font-family: var(--font-display);
|
| 461 |
|
- |
font-size: var(--font-size-4xl);
|
| 462 |
|
- |
font-weight: 700;
|
| 463 |
|
- |
color: var(--content);
|
| 464 |
|
- |
letter-spacing: -0.02em;
|
| 465 |
|
- |
}
|
| 466 |
|
- |
|
| 467 |
|
- |
.ui-mode-desktop .app-subtitle {
|
| 468 |
|
- |
font-size: var(--font-size-base);
|
| 469 |
|
- |
color: var(--content-muted);
|
| 470 |
|
- |
font-weight: 500;
|
| 471 |
|
- |
line-height: 1;
|
|
467 |
+ |
padding-bottom: var(--gap-peer);
|
|
468 |
+ |
/* The tabs own the left edge; everything else is pushed to the far end.
|
|
469 |
+ |
Not space-between: .header-content is empty on desktop and would take
|
|
470 |
+ |
the left slot, floating the tabs off the pane's edge. */
|
|
471 |
+ |
margin-left: auto;
|
| 472 |
472 |
|
}
|
| 473 |
473 |
|
|
| 474 |
474 |
|
.mobile-view-title {
|
| 476 |
476 |
|
}
|
| 477 |
477 |
|
|
| 478 |
478 |
|
/* 7. Tab Navigation (desktop UI only)
|
| 479 |
|
- |
The top tab strip is desktop-only; mobile UI uses the bottom tab bar. */
|
|
479 |
+ |
The top tab strip is desktop-only; mobile UI uses the bottom tab bar.
|
|
480 |
+ |
|
|
481 |
+ |
Folder tabs, not toggles. A pressed toggle says "this control is on" and
|
|
482 |
+ |
says nothing about what is below it; a folder tab's selected state removes
|
|
483 |
+ |
the line between itself and the pane, so the two read as one object. That
|
|
484 |
+ |
is the whole semantic, and it is why the tab carries no bevel: a bevel
|
|
485 |
+ |
makes it an object sitting on the strip rather than the front edge of the
|
|
486 |
+ |
pane. Tabs run from the left edge, which is the pane's left edge. */
|
| 480 |
487 |
|
.ui-mode-desktop .tab-navigation {
|
| 481 |
488 |
|
display: flex;
|
| 482 |
|
- |
justify-content: center;
|
| 483 |
|
- |
gap: var(--gap-peer);
|
|
489 |
+ |
align-items: flex-end;
|
|
490 |
+ |
gap: var(--gap-bound);
|
| 484 |
491 |
|
}
|
| 485 |
492 |
|
|
| 486 |
493 |
|
.ui-mode-desktop .tab {
|
| 489 |
496 |
|
gap: var(--gap-bound);
|
| 490 |
497 |
|
padding: var(--gap-peer) var(--gap-section);
|
| 491 |
498 |
|
text-decoration: none;
|
| 492 |
|
- |
color: var(--content);
|
| 493 |
|
- |
background: var(--surface-raised);
|
|
499 |
+ |
color: var(--content-secondary);
|
|
500 |
+ |
background: var(--surface-sunken);
|
| 494 |
501 |
|
border: var(--border-width) solid var(--border);
|
| 495 |
|
- |
border-radius: var(--radius-md);
|
|
502 |
+ |
border-bottom: 0;
|
|
503 |
+ |
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
| 496 |
504 |
|
font-weight: 600;
|
| 497 |
|
- |
box-shadow: var(--bevel-raised);
|
| 498 |
505 |
|
}
|
| 499 |
506 |
|
|
| 500 |
507 |
|
.ui-mode-desktop .tab:hover {
|
| 501 |
508 |
|
background: var(--surface-overlay);
|
|
509 |
+ |
color: var(--content);
|
| 502 |
510 |
|
}
|
| 503 |
511 |
|
|
| 504 |
|
- |
/* The selected tab is latched down, same inversion as a pressed button. */
|
|
512 |
+ |
/* The selected tab is filled with the pane's own surface and overlaps the
|
|
513 |
+ |
strip's bottom line, breaking it: tab and pane become one shape. The
|
|
514 |
+ |
specimen writes the overlap as `top: 1px` on the unselected tabs; a
|
|
515 |
+ |
negative bottom margin says the same thing with the direction explicit,
|
|
516 |
+ |
and the matching padding keeps every tab's label on one baseline. */
|
| 505 |
517 |
|
.ui-mode-desktop .tab.active {
|
| 506 |
|
- |
background-color: var(--action);
|
| 507 |
|
- |
color: var(--content-on-action);
|
| 508 |
|
- |
box-shadow: var(--bevel-inset);
|
|
518 |
+ |
position: relative;
|
|
519 |
+ |
background: var(--surface-raised);
|
|
520 |
+ |
color: var(--content);
|
|
521 |
+ |
margin-bottom: calc(-1 * var(--border-width));
|
|
522 |
+ |
padding-bottom: calc(var(--gap-peer) + var(--border-width));
|
| 509 |
523 |
|
}
|
| 510 |
524 |
|
|
| 511 |
525 |
|
.ui-mode-desktop .tab-label {
|
| 519 |
533 |
|
}
|
| 520 |
534 |
|
|
| 521 |
535 |
|
/* --- Pill Sub-Navigation (desktop UI only, mobile uses slide menu) --- */
|
|
536 |
+ |
/* An underline strip, not a second row of folder tabs. Two tab rows put two
|
|
537 |
+ |
competing "this contains what follows" claims on the screen and give back
|
|
538 |
+ |
the ambiguity the top row just resolved, so the sub-level says selected
|
|
539 |
+ |
with a rule under the label and nothing else. Classic Platinum did nest
|
|
540 |
+ |
tabs; this is a call, not a constraint (decided 2026-07-28). */
|
| 522 |
541 |
|
.ui-mode-desktop .pill-nav {
|
| 523 |
542 |
|
display: flex;
|
| 524 |
|
- |
align-items: center;
|
| 525 |
|
- |
gap: var(--gap-peer);
|
|
543 |
+ |
align-items: stretch;
|
|
544 |
+ |
gap: var(--gap-section);
|
| 526 |
545 |
|
padding: 0;
|
| 527 |
546 |
|
margin-bottom: var(--gap-section);
|
| 528 |
547 |
|
min-height: 2rem;
|
|
548 |
+ |
border-bottom: var(--border-width) solid var(--border);
|
| 529 |
549 |
|
}
|
| 530 |
550 |
|
|
| 531 |
551 |
|
.ui-mode-desktop .pill {
|
| 532 |
|
- |
padding: var(--gap-bound) var(--gap-section);
|
| 533 |
|
- |
border-radius: var(--radius-xl);
|
| 534 |
|
- |
border: var(--border-width-sm) solid var(--border);
|
| 535 |
|
- |
background: var(--surface-raised);
|
|
552 |
+ |
padding: var(--gap-bound) var(--gap-peer);
|
|
553 |
+ |
border: 0;
|
|
554 |
+ |
/* Sits on the strip's own line, so it has to outweigh it when lit. */
|
|
555 |
+ |
border-bottom: calc(var(--border-width) * 2) solid transparent;
|
|
556 |
+ |
margin-bottom: calc(-1 * var(--border-width));
|
|
557 |
+ |
border-radius: 0;
|
|
558 |
+ |
background: none;
|
|
559 |
+ |
box-shadow: none;
|
|
560 |
+ |
color: var(--content-secondary);
|
| 536 |
561 |
|
font-family: var(--font-sans);
|
| 537 |
562 |
|
font-size: var(--font-size-sm);
|
| 538 |
563 |
|
font-weight: 600;
|
| 539 |
564 |
|
cursor: pointer;
|
| 540 |
|
- |
box-shadow: var(--bevel-raised);
|
| 541 |
565 |
|
}
|
| 542 |
566 |
|
|
| 543 |
567 |
|
.ui-mode-desktop .pill:hover {
|
| 544 |
|
- |
background: var(--surface-sunken);
|
|
568 |
+ |
background: none;
|
|
569 |
+ |
color: var(--content);
|
| 545 |
570 |
|
}
|
| 546 |
571 |
|
|
| 547 |
572 |
|
.ui-mode-desktop .pill.active {
|
| 548 |
|
- |
background: var(--content);
|
| 549 |
|
- |
color: var(--surface-raised);
|
| 550 |
|
- |
border-color: var(--content);
|
| 551 |
|
- |
box-shadow: var(--bevel-inset);
|
|
573 |
+ |
background: none;
|
|
574 |
+ |
color: var(--content);
|
|
575 |
+ |
border-bottom-color: var(--action);
|
| 552 |
576 |
|
}
|
| 553 |
577 |
|
|
| 554 |
578 |
|
/* 8. Main Content & Page Header */
|
| 560 |
584 |
|
padding: var(--gap-pane) var(--gap-page) var(--gap-page);
|
| 561 |
585 |
|
}
|
| 562 |
586 |
|
|
|
587 |
+ |
/* The pane the tab strip belongs to. Same width as the strip (§6), so the
|
|
588 |
+ |
two share a left and a right edge; no top border, because the strip's
|
|
589 |
+ |
border-bottom already is that line and drawing it twice would show. */
|
|
590 |
+ |
.ui-mode-desktop .main-content {
|
|
591 |
+ |
background: var(--surface-raised);
|
|
592 |
+ |
border: var(--border-width) solid var(--border);
|
|
593 |
+ |
border-top: 0;
|
|
594 |
+ |
}
|
|
595 |
+ |
|
| 563 |
596 |
|
/* Page Header */
|
| 564 |
597 |
|
.page-header {
|
| 565 |
598 |
|
display: flex;
|
| 9147 |
9180 |
|
text-align: center;
|
| 9148 |
9181 |
|
}
|
| 9149 |
9182 |
|
|
| 9150 |
|
- |
/* On the active (inverted) pill the red badge loses contrast, so flip it to the
|
| 9151 |
|
- |
pill's own background. */
|
| 9152 |
|
- |
.ui-mode-desktop .pill.active .pill-count {
|
| 9153 |
|
- |
background: var(--surface-raised);
|
| 9154 |
|
- |
color: var(--content);
|
| 9155 |
|
- |
}
|
| 9156 |
|
- |
|
| 9157 |
9183 |
|
.view-hint {
|
| 9158 |
9184 |
|
margin: 0 0 var(--gap-section);
|
| 9159 |
9185 |
|
font-size: var(--font-size-sm);
|