CI / verify (pull_request) Successful in 1m13s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 48s
CI / migration-smoke (pull_request) Successful in 2m22s
CI / image-build (pull_request) Successful in 1m8s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / package-publication (pull_request) Successful in 57s
CI / deploy-smoke (split) (pull_request) Successful in 3m38s
The assembled Stage 1 Dashboard was reviewed by hand on a desktop, with the sidebar collapsed, and on an iPhone. This fixes what that review found, and records the decisions in ADR 0064: - Discover is its own page at /settings/discover, with Installed and Updates beneath it as children. There is no Browse item any more. /settings/discover/browse is still read, as an alias, and is respelled in place. Parents and children now read as a hierarchy: a guide line, text-only children, a disclosure chevron, and parent and current states that look different. - The sidebar is navigation only. Display pairing moves from the sidebar and drawer to Settings -> Displays: the paired displays, a deliberate Add display dialog, and revocation. Nothing takes focus on arrival; the code field is focused only once Add display is chosen. - Collapsed icons are centred. Nav-link layout moves from inline styles to the shell stylesheet, and every collapsed item is one square centred on the rail. - /settings is an overview of the installation: displays paired, modules installed and running, and a line for each other area. It reads only existing endpoints. - Phones get a sticky, safe-area-aware header holding the menu trigger and the page name. New pages start at the top, and focus no longer scrolls content under the header. - @ekklesia/ui draws checkbox and radio indicators from tokens rather than accent-color, so the selected state is the strong accent in Light and Dark alike. Co-Authored-By: Claude Opus 5.5 <[email protected]>
659 lines
20 KiB
TypeScript
659 lines
20 KiB
TypeScript
import { designToken } from "@ekklesia/design-tokens";
|
|
import { SHELL_ICON_SIZE } from "./shell-icons.js";
|
|
|
|
/** The phone header's own height: one large control plus a little room above and below it. */
|
|
const MOBILE_BAR_HEIGHT = `calc(${designToken("control.height-lg")} + 2 * ${designToken("spacing.xs")})`;
|
|
|
|
/**
|
|
* The Dashboard shell's own stylesheet (ADR 0058): what inline styles cannot express — hover and
|
|
* `:focus-visible` — for the shell's navigation, account menu, tooltip, and drawer. Durations are
|
|
* motion tokens, so reduced motion needs no rule here (ADR 0060).
|
|
*
|
|
* Every value is a design token (ADR 0039), never a literal colour. Every class is `ek-shell-`
|
|
* prefixed, so nothing here can collide with `@ekklesia/ui`'s `ek-` elements, and it is installed
|
|
* into the shell's own document only: each module artifact still mounts in its own isolated root
|
|
* (ADR 0040), which this stylesheet cannot reach.
|
|
*/
|
|
export const SHELL_STYLESHEET = `
|
|
.ek-shell-visually-hidden {
|
|
border: 0;
|
|
clip-path: inset(50%);
|
|
height: 1px;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
padding: 0;
|
|
position: absolute;
|
|
white-space: nowrap;
|
|
width: 1px;
|
|
}
|
|
|
|
/* The authenticated shell's frame. dvh where the browser has it, so a phone's collapsing browser
|
|
chrome never leaves the page a toolbar's height too tall. */
|
|
.ek-shell-root {
|
|
min-height: 100vh;
|
|
min-height: 100dvh;
|
|
}
|
|
|
|
/* The page's own region. A phone gets a slimmer margin than a desktop, and on a device with a notch
|
|
or a home indicator the page's edges keep clear of them. */
|
|
.ek-shell-main {
|
|
padding: ${designToken("spacing.xl")};
|
|
padding-bottom: calc(${designToken("spacing.xl")} + env(safe-area-inset-bottom, 0px));
|
|
padding-right: calc(${designToken("spacing.xl")} + env(safe-area-inset-right, 0px));
|
|
}
|
|
|
|
.ek-shell-root[data-layout="compact"] .ek-shell-main {
|
|
padding: ${designToken("spacing.lg")};
|
|
padding-bottom: calc(${designToken("spacing.lg")} + env(safe-area-inset-bottom, 0px));
|
|
padding-left: calc(${designToken("spacing.lg")} + env(safe-area-inset-left, 0px));
|
|
padding-right: calc(${designToken("spacing.lg")} + env(safe-area-inset-right, 0px));
|
|
}
|
|
|
|
.ek-shell-sidebar {
|
|
background: ${designToken("background.canvas")};
|
|
border-right: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: ${designToken("spacing.md")};
|
|
height: 100vh;
|
|
height: 100dvh;
|
|
padding: ${designToken("spacing.md")};
|
|
/* A landscape phone wide enough for the sidebar puts its left edge beside the notch, and its
|
|
account area above the home indicator. */
|
|
padding-bottom: calc(${designToken("spacing.md")} + env(safe-area-inset-bottom, 0px));
|
|
padding-left: calc(${designToken("spacing.md")} + env(safe-area-inset-left, 0px));
|
|
position: sticky;
|
|
top: 0;
|
|
transition: width ${designToken("motion.duration-short")} ease;
|
|
}
|
|
|
|
.ek-shell-sidebar__top {
|
|
align-items: center;
|
|
display: flex;
|
|
gap: ${designToken("spacing.sm")};
|
|
justify-content: space-between;
|
|
min-height: ${designToken("control.height-md")};
|
|
}
|
|
|
|
.ek-shell-sidebar[data-sidebar="collapsed"] .ek-shell-sidebar__top {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.ek-shell-sidebar__scroll {
|
|
display: grid;
|
|
flex: 1 1 auto;
|
|
align-content: start;
|
|
gap: ${designToken("spacing.xl")};
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* The rail is exactly as wide as its items. A scrollbar would take its width from one side and push
|
|
every icon off centre, so the rail scrolls without one (wheel, touch, and keyboard focus still
|
|
scroll it). */
|
|
.ek-shell-sidebar[data-sidebar="collapsed"] .ek-shell-sidebar__scroll {
|
|
scrollbar-width: none;
|
|
}
|
|
|
|
.ek-shell-sidebar[data-sidebar="collapsed"] .ek-shell-sidebar__scroll::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.ek-shell-sidebar__bottom {
|
|
border-top: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
padding-top: ${designToken("spacing.sm")};
|
|
position: relative;
|
|
}
|
|
|
|
.ek-shell-brand {
|
|
align-items: center;
|
|
color: ${designToken("text.primary")};
|
|
display: flex;
|
|
font-size: ${designToken("typography.size-md")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
gap: ${designToken("spacing.sm")};
|
|
min-width: 0;
|
|
}
|
|
|
|
.ek-shell-brand__mark {
|
|
background: ${designToken("accent.base")};
|
|
border-radius: ${designToken("radius.md")};
|
|
box-shadow: inset 0 0 0 ${designToken("border.width")} ${designToken("accent.active")};
|
|
display: block;
|
|
flex: 0 0 auto;
|
|
height: ${designToken("spacing.xl")};
|
|
width: ${designToken("spacing.xl")};
|
|
}
|
|
|
|
.ek-shell-nav,
|
|
.ek-shell-nav__group {
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
}
|
|
|
|
.ek-shell-nav {
|
|
gap: ${designToken("spacing.lg")};
|
|
}
|
|
|
|
.ek-shell-nav__group-label {
|
|
color: ${designToken("text.muted")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
letter-spacing: 0.05em;
|
|
margin: 0 0 ${designToken("spacing.2xs")};
|
|
padding-inline: ${designToken("control.padding-inline")};
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.ek-shell-nav__nested {
|
|
border-inline-start: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
display: grid;
|
|
gap: ${designToken("spacing.xs")};
|
|
margin-block-start: ${designToken("spacing.xs")};
|
|
margin-inline-start: ${designToken("spacing.lg")};
|
|
padding-inline-start: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
/* A parent's children — a module's pages, Discover's Installed and Updates (ADR 0064). The guide
|
|
line runs down from beneath the parent's icon, and the children's text starts past the parent's
|
|
label, so the relationship reads at a glance. */
|
|
.ek-shell-nav__children {
|
|
border-inline-start: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
margin: ${designToken("spacing.2xs")} 0 ${designToken("spacing.xs")}
|
|
calc(${designToken("control.padding-inline")} + ${SHELL_ICON_SIZE / 2}px);
|
|
padding: 0 0 0 ${designToken("spacing.lg")};
|
|
}
|
|
|
|
.ek-shell-nav__divider {
|
|
border-top: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
margin: 0 ${designToken("spacing.xs")};
|
|
}
|
|
|
|
/* A navigation link (DashboardLink). Its layout and open-page look live here, never inline, so
|
|
the collapsed rail below can override them (ADR 0064). */
|
|
.ek-shell-link {
|
|
justify-content: flex-start;
|
|
text-decoration: none;
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-shell-link[aria-current="page"],
|
|
.ek-shell-link[aria-current="page"]:hover:not(:disabled) {
|
|
background: ${designToken("state.selected-background")};
|
|
color: ${designToken("state.selected-foreground")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
}
|
|
|
|
/* The parent of the open page: named in the primary colour, its icon in the accent, and no fill —
|
|
the fill belongs to the open page alone. */
|
|
.ek-shell-link[data-nav-ancestor] {
|
|
color: ${designToken("text.primary")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
}
|
|
|
|
.ek-shell-link[data-nav-ancestor] > svg {
|
|
color: ${designToken("accent.strong")};
|
|
}
|
|
|
|
.ek-shell-nav-item {
|
|
box-sizing: border-box;
|
|
gap: ${designToken("spacing.sm")};
|
|
height: auto;
|
|
justify-content: flex-start;
|
|
text-align: start;
|
|
}
|
|
|
|
.ek-shell-nav-item svg {
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.ek-shell-nav-item[data-nav-level="child"]:not([aria-current="page"]) {
|
|
font-weight: ${designToken("typography.weight-regular")};
|
|
}
|
|
|
|
.ek-shell-nav-item__label {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.ek-shell-nav-item__disclosure {
|
|
color: ${designToken("text.muted")};
|
|
display: inline-flex;
|
|
flex: 0 0 auto;
|
|
transition: transform ${designToken("motion.duration-short")} ease;
|
|
}
|
|
|
|
.ek-shell-nav-item__disclosure[data-open="true"] {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
/* Collapsed, every item is the same square, centred on the rail, with its glyph centred in it —
|
|
whatever its level, and whatever the expanded layout's padding, gap, or alignment would say. */
|
|
.ek-shell-sidebar[data-sidebar="collapsed"] .ek-shell-nav-item {
|
|
/* A block-level flex box, so the auto margins centre it on the rail. */
|
|
display: flex;
|
|
gap: 0;
|
|
height: ${designToken("control.height-md")};
|
|
justify-content: center;
|
|
margin-inline: auto;
|
|
padding: 0;
|
|
width: ${designToken("control.height-md")};
|
|
}
|
|
|
|
.ek-shell-settings-link {
|
|
align-items: center;
|
|
border-radius: ${designToken("radius.md")};
|
|
box-sizing: border-box;
|
|
color: ${designToken("text.primary")};
|
|
display: flex;
|
|
gap: ${designToken("spacing.md")};
|
|
padding: ${designToken("spacing.sm")} ${designToken("spacing.md")};
|
|
text-decoration: none;
|
|
}
|
|
|
|
.ek-shell-settings-link:hover {
|
|
background: ${designToken("state.hover-background")};
|
|
}
|
|
|
|
.ek-shell-settings-link:focus-visible {
|
|
outline: ${designToken("state.focus-visible-width")} solid ${designToken("accent.focus")};
|
|
outline-offset: ${designToken("state.focus-visible-offset")};
|
|
}
|
|
|
|
.ek-shell-settings-link svg {
|
|
color: ${designToken("text.secondary")};
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.ek-shell-settings-link__copy {
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
min-width: 0;
|
|
}
|
|
|
|
.ek-shell-settings-link__label {
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
}
|
|
|
|
.ek-shell-settings-link__description {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
}
|
|
|
|
/* Settings → Displays: one card per paired screen, wrapping onto two lines on a phone. */
|
|
.ek-shell-display-list {
|
|
display: grid;
|
|
gap: ${designToken("spacing.sm")};
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.ek-shell-display-card {
|
|
align-items: center;
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.md")};
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: ${designToken("spacing.sm")} ${designToken("spacing.md")};
|
|
padding: ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-shell-display-card__icon {
|
|
align-items: center;
|
|
background: ${designToken("background.inset")};
|
|
border-radius: ${designToken("radius.md")};
|
|
color: ${designToken("text.secondary")};
|
|
display: inline-flex;
|
|
flex: 0 0 auto;
|
|
height: ${designToken("control.height-md")};
|
|
justify-content: center;
|
|
width: ${designToken("control.height-md")};
|
|
}
|
|
|
|
.ek-shell-display-card__copy {
|
|
display: grid;
|
|
flex: 1 1 12rem;
|
|
gap: ${designToken("spacing.2xs")};
|
|
min-width: 0;
|
|
}
|
|
|
|
.ek-shell-display-card__title {
|
|
align-items: center;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-shell-display-card__name {
|
|
color: ${designToken("text.primary")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.ek-shell-display-card__facts {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
margin: 0;
|
|
}
|
|
|
|
/* The Settings overview (ADR 0064): what state the installation is in, a card per area. */
|
|
.ek-shell-overview {
|
|
display: grid;
|
|
gap: ${designToken("spacing.md")};
|
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
|
|
}
|
|
|
|
.ek-shell-overview--compact {
|
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
|
|
}
|
|
|
|
.ek-shell-summary {
|
|
background: ${designToken("background.surface")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.lg")};
|
|
box-shadow: ${designToken("shadow.sm")};
|
|
box-sizing: border-box;
|
|
display: grid;
|
|
gap: ${designToken("spacing.sm")};
|
|
grid-template-rows: auto 1fr auto;
|
|
min-width: 0;
|
|
padding: ${designToken("spacing.lg")};
|
|
}
|
|
|
|
.ek-shell-summary__heading {
|
|
align-items: center;
|
|
color: ${designToken("text.secondary")};
|
|
display: flex;
|
|
font-size: ${designToken("typography.size-sm")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
gap: ${designToken("spacing.sm")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-shell-summary__status {
|
|
color: ${designToken("text.primary")};
|
|
font-size: ${designToken("typography.size-lg")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-shell-summary--compact .ek-shell-summary__status {
|
|
font-size: ${designToken("typography.size-md")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
}
|
|
|
|
.ek-shell-summary__detail {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-shell-summary__links {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: ${designToken("spacing.xs")} ${designToken("spacing.md")};
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.ek-shell-summary__link {
|
|
border-radius: ${designToken("radius.sm")};
|
|
color: ${designToken("accent.strong")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
text-decoration: none;
|
|
}
|
|
|
|
.ek-shell-summary__link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.ek-shell-summary__link:focus-visible {
|
|
outline: ${designToken("state.focus-visible-width")} solid ${designToken("accent.focus")};
|
|
outline-offset: ${designToken("state.focus-visible-offset")};
|
|
}
|
|
|
|
.ek-shell-tooltip {
|
|
background: ${designToken("background.raised")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.sm")};
|
|
box-shadow: ${designToken("shadow.md")};
|
|
color: ${designToken("text.primary")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
padding: ${designToken("spacing.2xs")} ${designToken("spacing.sm")};
|
|
position: fixed;
|
|
transform: translateY(-50%);
|
|
white-space: nowrap;
|
|
z-index: 30;
|
|
}
|
|
|
|
.ek-shell-menu {
|
|
background: ${designToken("background.raised")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.md")};
|
|
box-shadow: ${designToken("shadow.md")};
|
|
bottom: calc(100% + ${designToken("spacing.xs")});
|
|
box-sizing: border-box;
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
left: 0;
|
|
margin: 0;
|
|
max-width: min(20rem, calc(100vw - 2 * ${designToken("spacing.md")}));
|
|
min-width: 12rem;
|
|
padding: ${designToken("spacing.xs")};
|
|
position: absolute;
|
|
/* As wide as a name and email need, within the cap, rather than the width of the sidebar. */
|
|
width: max-content;
|
|
z-index: 20;
|
|
}
|
|
|
|
.ek-shell-menu__caption {
|
|
color: ${designToken("text.muted")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
margin: 0;
|
|
padding: ${designToken("spacing.2xs")} ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-shell-menu__item {
|
|
justify-content: flex-start;
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-shell-menu__items {
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
}
|
|
|
|
.ek-shell-menu__identity {
|
|
border-bottom: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
min-width: 0;
|
|
padding: ${designToken("spacing.xs")} ${designToken("spacing.sm")} ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-shell-menu__identity-name {
|
|
color: ${designToken("text.primary")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.ek-shell-menu__identity-email {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.ek-shell-account-trigger__copy {
|
|
display: grid;
|
|
min-width: 0;
|
|
text-align: start;
|
|
}
|
|
|
|
.ek-shell-choice-group {
|
|
border: 0;
|
|
margin: 0;
|
|
min-width: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.ek-shell-choice-group__legend {
|
|
color: ${designToken("text.primary")};
|
|
font-size: ${designToken("typography.size-heading-3")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
padding: 0;
|
|
}
|
|
|
|
.ek-shell-choice-group__description {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
margin: ${designToken("spacing.2xs")} 0 ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-shell-choice-group__options {
|
|
display: grid;
|
|
gap: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
/* One option of a radio group: the whole row is the label, so the whole row is the touch target.
|
|
The chosen option is marked by the radio itself and by weight as well as by its background. */
|
|
.ek-shell-choice {
|
|
align-items: flex-start;
|
|
border: ${designToken("border.width")} solid ${designToken("border.control")};
|
|
border-radius: ${designToken("radius.md")};
|
|
box-sizing: border-box;
|
|
color: ${designToken("text.primary")};
|
|
cursor: pointer;
|
|
display: flex;
|
|
gap: ${designToken("spacing.md")};
|
|
padding: ${designToken("spacing.sm")} ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-shell-choice:hover {
|
|
background: ${designToken("state.hover-background")};
|
|
}
|
|
|
|
.ek-shell-choice:has(.ek-shell-choice__input:focus-visible) {
|
|
outline: ${designToken("state.focus-visible-width")} solid ${designToken("accent.focus")};
|
|
outline-offset: ${designToken("state.focus-visible-offset")};
|
|
}
|
|
|
|
.ek-shell-choice[data-checked="true"] {
|
|
background: ${designToken("state.selected-background")};
|
|
color: ${designToken("state.selected-foreground")};
|
|
}
|
|
|
|
/* The radio itself is @ekklesia/ui's shared indicator (ek-radio__input), larger here because the
|
|
whole row is the target and the radio is its one visual anchor. */
|
|
.ek-shell-choice .ek-shell-choice__input {
|
|
--ek-choice-size: 1.25rem;
|
|
margin: 0.125rem 0 0;
|
|
}
|
|
|
|
/* The whole row carries the focus ring (above), so the radio inside it draws none of its own. */
|
|
.ek-shell-choice .ek-shell-choice__input:focus-visible {
|
|
outline: none;
|
|
}
|
|
|
|
.ek-shell-choice__copy {
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
min-width: 0;
|
|
}
|
|
|
|
.ek-shell-choice__label {
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
}
|
|
|
|
.ek-shell-choice[data-checked="true"] .ek-shell-choice__label {
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
}
|
|
|
|
.ek-shell-choice__description {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
}
|
|
|
|
.ek-shell-choice[data-checked="true"] .ek-shell-choice__description {
|
|
color: ${designToken("state.selected-foreground")};
|
|
}
|
|
|
|
.ek-shell-account-trigger__email {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
}
|
|
|
|
/* The phone header (ADR 0064): the menu trigger and the open page's name, kept at the top while the
|
|
page scrolls, below the status bar or notch (viewport-fit=cover). Not a desktop top bar: it
|
|
holds nothing else. */
|
|
.ek-shell-mobile-bar {
|
|
align-items: center;
|
|
background: ${designToken("background.canvas")};
|
|
border-bottom: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
gap: ${designToken("spacing.sm")};
|
|
min-height: calc(${MOBILE_BAR_HEIGHT} + env(safe-area-inset-top, 0px));
|
|
padding: calc(${designToken("spacing.xs")} + env(safe-area-inset-top, 0px))
|
|
calc(${designToken("spacing.sm")} + env(safe-area-inset-right, 0px))
|
|
${designToken("spacing.xs")}
|
|
calc(${designToken("spacing.sm")} + env(safe-area-inset-left, 0px));
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
.ek-shell-mobile-bar__title {
|
|
color: ${designToken("text.primary")};
|
|
font-size: ${designToken("typography.size-md")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Something scrolled into view — a focused field, a fragment — lands below the header, not under it. */
|
|
html:has(.ek-shell-mobile-bar) {
|
|
scroll-padding-top: calc(${MOBILE_BAR_HEIGHT} + env(safe-area-inset-top, 0px) + ${designToken("spacing.sm")});
|
|
}
|
|
|
|
.ek-shell-drawer-backdrop {
|
|
background: ${designToken("background.overlay")};
|
|
inset: 0;
|
|
position: fixed;
|
|
z-index: 40;
|
|
}
|
|
|
|
.ek-shell-drawer {
|
|
background: ${designToken("background.canvas")};
|
|
box-shadow: ${designToken("shadow.lg")};
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: ${designToken("spacing.md")};
|
|
inset: 0 auto 0 0;
|
|
max-width: 85vw;
|
|
outline: none;
|
|
padding: ${designToken("spacing.md")};
|
|
padding-bottom: calc(${designToken("spacing.md")} + env(safe-area-inset-bottom, 0px));
|
|
padding-left: calc(${designToken("spacing.md")} + env(safe-area-inset-left, 0px));
|
|
padding-top: calc(${designToken("spacing.md")} + env(safe-area-inset-top, 0px));
|
|
position: fixed;
|
|
width: 20rem;
|
|
z-index: 41;
|
|
}
|
|
|
|
/* The sidebar's transition lasts a motion token, which is zero when motion is reduced (ADR 0060). */
|
|
`;
|