/**
 * shell shared component vocabulary. Every page links tokens.css then this.
 * Custom (unregistered) tags are used throughout, styled here and queried by
 * each page's controller — the same convention as the gateway UI.
 */

@import url("./tokens.css");
@import url("https://fonts.googleapis.com/css2?family=Material+Icons");

/* ---- Page shell -------------------------------------------------------- */

/* The shell opts in to edge-to-edge rendering (viewport-fit=cover in every
   page's viewport meta), so installed/standalone displays draw behind the
   status bar and any display cutout — pad clear of them. The bottom inset
   (gesture nav bar, iOS home indicator) is handled by the actionbar. */
html {
  height: 100%;
}

body {
  min-height: 100vh;
  padding-top: env(safe-area-inset-top);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

main,
.page {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 1.25rem 1.5rem calc(3rem + env(safe-area-inset-bottom));
}

/* ---- Standard page header (h1 + subhead + actions) --------------------- */

/* nowrap: the actions stay top-right on the h1's line however narrow the
   display — the titles column shrinks and ellipsizes instead of pushing
   them onto a new line. */
pagehead {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  flex-wrap: nowrap;
  padding-bottom: 0.85rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--border);
}

/* Standard leading Back control (top-left), wired by layout.js initHeader({back}).
   Goes up one level to the page's logical parent. Hidden on the root launcher. */
pagehead > back {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  margin-top: -0.1rem;
  border-radius: var(--radius-sm);
  color: var(--muted);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}

pagehead > back:hover {
  color: var(--text);
  background: var(--panel-2);
}

/* Page actions sit at the far right, leaving Back + titles grouped on the left. */
pagehead actions {
  margin-left: auto;
}

pagehead titles {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

pagehead h1 {
  margin: 0;
  font-size: 1.35rem;
  font-weight: 600;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The standardised subheading slot. Filled with the party name by default
   (layout.js), overridable per page via setSubhead(). */
pagehead subhead {
  color: var(--muted);
  font-size: 0.85rem;
  font-family: var(--mono);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

pagehead subhead:empty {
  display: none;
}

/* ---- Actions bar (icon buttons) --------------------------------------- */

actions {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-shrink: 0;
}

actions > * {
  cursor: pointer;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: var(--radius-sm);
  transition: background 0.12s, color 0.12s;
}

actions > *:hover {
  color: var(--text);
  background: var(--panel-2);
}

actions > a {
  text-decoration: none;
}

actions img {
  width: 1.35rem;
  height: 1.35rem;
  opacity: 0.7;
}

@media (prefers-color-scheme: dark) {
  actions img {
    filter: invert(1);
  }
}

actions > *:hover img {
  opacity: 1;
}

/* ---- Pinned action bar ------------------------------------------------- */
/* List pages opt in with body.pinned: the page fills the viewport, the
   <scroll> region between the pagehead and the <actionbar> scrolls, and
   the bar's labelled buttons stay visible at the bottom. */

body.pinned {
  /* Sized as 100% of the ICB (html), NOT viewport units: in installed
     (standalone) mode on Android, vh/dvh resolve to the window extent
     behind the system nav bar while safe-area-inset-bottom reports 0 —
     viewport units would push the actionbar into that hidden strip. The
     ICB tracks the actual visible viewport in every mode. min-height: 0
     cancels the page-shell min-height: 100vh, which would win otherwise. */
  min-height: 0;
  height: 100%;
  overflow: hidden;
}

body.pinned main {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding-bottom: 0;
}

body.pinned state {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

body.pinned scroll {
  display: block;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Padded clear of the home indicator when installed on iOS. */
actionbar {
  display: flex;
  justify-content: space-around;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.75rem 0;
  padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
}

/* Buttons size to their label, with a floor, spread across the bar. */
actionbar > * {
  min-width: 5rem;
}

/* Material-icon glyph rendered from element text content (e.g. tile icons). */
.material-icons {
  font-family: "Material Icons";
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  -webkit-font-feature-settings: "liga";
}

/* Material-icon glyph rendered from the element's icon="" attribute. */
[icon]::before {
  font-family: "Material Icons";
  font-size: 1.4rem;
  line-height: 1;
  content: attr(icon);
}

[icon].noshow {
  visibility: hidden;
}

/* ---- Detail pages ------------------------------------------------------ */
/* Shared label/value layout for item detail pages (device, daemon). */

detail {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  max-width: 640px;
}

detail fields {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.5rem 1rem;
  align-items: baseline;
}

detail fields label {
  color: var(--muted);
  font-size: 0.85rem;
}

detail fields value {
  overflow-wrap: anywhere;
}

detail fields value.mono {
  font-family: var(--mono);
  font-size: 0.9rem;
}

detail fields value a {
  color: var(--accent);
}

detail field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

detail field label {
  color: var(--muted);
  font-size: 0.85rem;
}

/* Detail-page actions: each is a labelled button beside a one-line
   explanation; the fixed button column keeps the explanations aligned. */
detail actionrows {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

detail actionrows action {
  display: grid;
  grid-template-columns: 10rem minmax(0, 1fr);
  gap: 1rem;
  align-items: center;
}

detail pre[name="payload"] {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.75rem;
  margin: 0;
  overflow: auto;
  max-height: 60vh;
  font-family: var(--mono);
  font-size: 0.82rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ---- Cards & card grid ------------------------------------------------- */

cards,
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
}

card,
.card {
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted);
  border-radius: var(--radius);
  overflow: hidden;
}

card[data-state="STARTED"],
.card[data-state="STARTED"] {
  border-left-color: var(--warn);
}
card[data-state="READY"],
.card[data-state="READY"] {
  border-left-color: var(--ok);
}
card[data-state="ERROR"],
.card[data-state="ERROR"] {
  border-left-color: var(--err);
}

cardheader,
.cardheader {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}

cardbody,
.cardbody {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.7rem 0.85rem;
}

cardcontent,
.cardcontent {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  color: var(--muted);
  font-size: 0.85rem;
  min-width: 0;
}

cardactions,
.cardactions {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

cardactions > *,
.cardactions > * {
  cursor: pointer;
  color: var(--muted);
  transition: color 0.12s;
}

cardactions > *:hover,
.cardactions > *:hover {
  color: var(--accent);
}

/* ---- App / tile grid (Home, System hub) ------------------------------- */

tiles,
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  gap: 1rem;
}

app {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 0.9rem 0.5rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.12s, transform 0.12s;
}

app:hover {
  border-color: var(--accent);
}

appicon {
  width: 56px;
  height: 56px;
  border-radius: 12px;
  background-color: var(--panel-2);
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 40px;
  color: var(--muted);
}

/* Glyph tiles (System hub) render a Material Icon rather than an image, so
   drop the placeholder box and centre the larger accent-coloured glyph. The
   `appicon.material-icons` specificity beats the imported Material Icons
   `.material-icons { font-size: 24px }` default. */
appicon.material-icons {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  font-size: 42px;
  color: var(--accent);
}

/* Fallback for an app with no icon: a two-letter monogram (from the name) on
   the placeholder box. Token colour adapts to light/dark; 0.6em scales the
   letters with each context's appicon font-size (tile vs. row). */
appicon.initials {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.6em;
  letter-spacing: 0.02em;
  color: var(--muted);
}

appname {
  font-size: 0.82rem;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* ---- Lists / tables ---------------------------------------------------- */

list,
.list {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

listhead,
.listhead {
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--panel-2);
}

row,
.row {
  display: grid;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.85rem;
  border-top: 1px solid var(--border);
}

listhead.row,
listhead row {
  border-top: 0;
}

list > row:first-child,
.list > .row:first-child {
  border-top: 0;
}

row.click,
.row.click {
  cursor: pointer;
}

row.click:hover,
.row.click:hover {
  background: var(--panel-2);
}

/* A selected row: an accent wash and an accent stripe, the stripe as an inset
   shadow so selecting shifts no layout. The wash rather than --panel-2,
   which is the hover colour — a selection a pointer has just left has to
   look different from one it is merely resting on. Pages that select rows
   (drive, grants) toggle `selected` and leave the look to this. */
row.selected,
.row.selected,
row.selected:hover,
.row.selected:hover {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}

row .muted,
.muted {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.85rem;
}

/* Sortable header cells (managed by lib/Sortable.js). The active column shows
   a ▲/▼ indicator via data-sort-direction. */
.listhead [data-sort] {
  cursor: pointer;
  user-select: none;
}

.listhead [data-sort]:hover {
  color: var(--text);
}

.listhead [data-sort][data-sort-direction="asc"]::after {
  content: " ▲";
  font-size: 0.7em;
}

.listhead [data-sort][data-sort-direction="desc"]::after {
  content: " ▼";
  font-size: 0.7em;
}

/* ---- Forms ------------------------------------------------------------- */

form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 420px;
}

label {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.85rem;
  color: var(--muted);
}

input,
select,
textarea {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 0.5rem 0.65rem;
  font-size: 0.9rem;
  font-family: inherit;
}

input:disabled,
select:disabled,
textarea:disabled {
  opacity: 0.6;
}

button,
a.button {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.9rem;
  font-size: 0.9rem;
  font-family: inherit;
  cursor: pointer;
  text-align: center;
}

button.secondary,
a.button.secondary {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
}

/* Destructive actions: outlined in the error colour rather than solid,
   so they read as available but demand a deliberate choice. */
button.danger,
a.button.danger {
  background: var(--panel-2);
  color: var(--err);
  border: 1px solid var(--err);
}

button:disabled,
a.button:disabled {
  opacity: 0.6;
  cursor: default;
}

buttons,
.buttons {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
}

/* ---- Feedback / states ------------------------------------------------- */

.intro,
intro {
  color: var(--muted);
  font-size: 0.9rem;
}

error {
  display: block;
  color: var(--err);
  font-size: 0.85rem;
}

empty {
  display: block;
  color: var(--muted);
  font-size: 0.9rem;
  padding: 1.5rem 0;
  text-align: center;
}

empty:not(:last-child) {
  display: none;
}

/* ---- Tab lists --------------------------------------------------------- */
/* Underline-style tabs paired with wireTabs() (memory/Tabs.js): buttons carry
   data-tab, panels carry data-panel, and `.selected` marks the active pair.
   Each page supplies its own panel show/hide (scoped to its panel wrapper). */

tablist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  align-items: flex-end;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}

tablist button {
  background: none;
  color: var(--muted);
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  padding: 0.5rem 0.85rem;
  margin-bottom: -1px;
  white-space: nowrap;
}

tablist button:hover {
  color: var(--text);
}

tablist button.selected {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* Busy state (browser.js setBusy toggles body.busy). */
body.busy {
  cursor: progress;
}

body.busy input,
body.busy button,
body.busy select {
  opacity: 0.6;
  pointer-events: none;
}

/* ---- Full-bleed tables -------------------------------------------------- */

/* A wide table earns the whole viewport even where its page is capped and
   centred by the `main` rule above: the negative inline margins cancel the
   cap, and the padding puts back the page's own gutter. Percentage margins
   resolve against the containing block's width, so once the viewport is
   narrower than the cap this comes out at the gutter it already had — the
   rule is inert on a phone. Everything else on the page stays capped.

   Marked in the markup rather than by page selector, since which elements
   deserve the full width is a per-page judgement, not a layout rule. */
.fullbleed {
  margin-inline: calc(50% - 50vw);
  padding-inline: 1.25rem;
}

/* ---- Rendered markdown -------------------------------------------------- */

/* Shared styling for anything rendered by lib/Markdown.js: put `markdown` on
   the element the fragment is appended to. The renderer emits ordinary block
   elements, so all this does is give them spacing that suits being embedded
   in a card, panel or dialog rather than a document. */

.markdown > :first-child {
  margin-top: 0;
}

.markdown > :last-child {
  margin-bottom: 0;
}

.markdown p,
.markdown ul,
.markdown ol {
  margin: 0.4rem 0;
}

.markdown ul,
.markdown ol {
  padding-left: 1.25rem;
}

.markdown h1,
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
  margin: 0.6rem 0 0.3rem;
  font-size: 1em;
  font-weight: 600;
}

.markdown code {
  font-family: var(--mono);
  font-size: 0.9em;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 0.25em;
}

.markdown pre {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.6rem;
  margin: 0.4rem 0;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 0.82rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.markdown table {
  border-collapse: collapse;
  margin: 0.4rem 0;
}

.markdown th,
.markdown td {
  border: 1px solid var(--border);
  padding: 0.25rem 0.5rem;
  text-align: left;
}

/* ---- Dialog ------------------------------------------------------------ */

dialog {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.25rem;
  max-width: 90vw;
}

dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px);
}

/* The shared Errors dialog (lib/Errors.js) renders
   <content><type><message><form><button>Close</button></form>. Stack them so
   the Close button sits below the message, with the message readable. */
dialog content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  max-width: min(90vw, 32rem);
}

dialog content type {
  font-weight: 600;
}

dialog content message {
  color: var(--text);
  text-align: center;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

dialog content form {
  margin: 0;
}

/* The shared content dialog (lib/ContentDialog.js) shows the full text
   behind an elided table cell. The centring, panel and backdrop come from
   the `dialog` rules above; this widens the panel and caps the text's
   height so long content scrolls in place rather than growing the dialog
   past the viewport. */
dialog[name="content"] content {
  max-width: min(92vw, 48rem);
  align-items: stretch;
}

dialog[name="content"] contentmeta {
  color: var(--muted);
  font-size: 0.85rem;
}

dialog[name="content"] contenttext {
  display: block;
  max-height: 60vh;
  overflow: auto;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-2);
  font-family: var(--mono);
  font-size: 0.85rem;
  text-align: left;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

dialog[name="content"] form {
  align-self: center;
}

/* The dialog's plain view is monospace and pre-wrap; markdown reads as prose
   instead, and its code sits on `--panel` for contrast against the panel-2
   text box it is inside. */
dialog[name="content"] contenttext.markdown {
  font-family: inherit;
  font-size: 1rem;
  white-space: normal;
}

dialog[name="content"] contenttext.markdown code,
dialog[name="content"] contenttext.markdown pre {
  background: var(--panel);
}

/* ---- QR / media -------------------------------------------------------- */

img.qrcode,
img[name="qrcode"] {
  width: 160px;
  border-radius: var(--radius);
  background: #fff;
  padding: 6px;
}

/* Camera preview for the QR scanner: shown only while scanning (.active),
   as a square overlay centred in the viewport over a dimmed page (the
   oversized box-shadow is the backdrop). Centred with inset/margin
   rather than a transform because the scanner library sets the video's
   transform itself to mirror front cameras. Scoped by name so other
   video elements (e.g. the drive file view) are not hidden. */
video[name="scanner"] {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(80vw, 80vh, 360px);
  aspect-ratio: 1;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.6);
  z-index: 10;
}

video[name="scanner"]:not(.active) {
  display: none;
}
