* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
  --bg: #0a0a0f; --surface: #14141f; --border: #2a2a3f; --text: #e0e0f0;
  --muted: #8888aa; --accent: #7b5cff; --accent2: #ff5caa; --glow: rgba(123,92,255,.35);
  /* theme-overridable chrome details (gifos-themes.js) */
  --bgglow: #1a1330; --bar: rgba(20,20,31,.75); --label: var(--text);
  --labelshadow: rgba(0,0,0,.85);
}
html, body { height: 100%; }
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  background: radial-gradient(1200px 800px at 70% -10%, var(--bgglow), var(--bg));
  color: var(--text); overflow: hidden; user-select: none;
}

/* update bar: js/gifos-update.js injects its CSS (shared with run.html, sign.html) */

/* status pills on the version rows (running / latest / critical) */
.vpill { display: inline-block; font-size: .66rem; font-weight: 700;
  letter-spacing: .03em; text-transform: uppercase; border-radius: .3rem; padding: .05rem .35rem; vertical-align: middle; }
.vpill.live { color: #fff; background: color-mix(in srgb, #34c759 78%, #000); }
.vpill.run { color: var(--accent); border: 1px solid var(--accent); }
.vpill.crit { color: #fff; background: #d33; }
.vbuild { color: var(--muted); font-size: .72rem; font-variant-numeric: tabular-nums; }

/* version list in Settings — each row folds its release notes behind it */
.vlist { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .3rem; }
.vrow { border: 1px solid var(--border); border-radius: .5rem; font-size: .85rem; overflow: hidden; }
.vrow .vhead { display: flex; align-items: center; gap: .5rem; padding: .45rem .7rem; }
.vrow > summary.vhead { cursor: pointer; list-style: none; }
.vrow > summary.vhead::-webkit-details-marker { display: none; }
.vrow > summary.vhead::marker { content: ''; }
.vrow-plain .vhead { cursor: default; }
.vlabel { display: flex; align-items: center; flex-wrap: wrap; gap: .1rem .35rem; min-width: 0; }
.vspacer { flex: 1 1 auto; }
/* Big left-hand disclosure triangle: a large, easy tap target far from the
   Roll-back button on the right, so unfolding notes never risks a rollback. */
.vcaret { flex: 0 0 auto; width: 1.1rem; text-align: center; color: var(--muted);
  font-size: 1.05rem; line-height: 1; transition: transform .15s ease; }
.vrow[open] > summary .vcaret { transform: rotate(90deg); }
.vcaret-empty { visibility: hidden; }
.vnotes { padding: .15rem .8rem .7rem; border-top: 1px solid var(--border); }
.vedge { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.vedge > summary.vhead { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.vbtn { padding: .3rem .7rem; border-radius: .4rem; border: 1px solid var(--accent);
  background: none; color: var(--accent); cursor: pointer; font-size: .78rem; white-space: nowrap; }
.vbtn:hover { background: rgba(123,92,255,.15); }
/* Secondary look (the "Re-pull" edge button) but still full-contrast text — a
   solid surface fill so it reads on the tinted edge row in light AND dark. */
.vbtn.ghost { background: var(--surface); border-color: var(--border); color: var(--text); }
.vbtn.ghost:hover { border-color: var(--accent); background: var(--surface); }

/* update bar: louder when a critical release is waiting */
.update-bar.critical { background: linear-gradient(135deg, rgba(255,92,92,.28), rgba(255,150,60,.28)); border-bottom-color: #ff6b6b; }

/* release notes, shown inside a row's fold */
.cl-date { color: var(--muted); font-size: .74rem; }
.cl-headline { font-size: .82rem; font-weight: 600; margin: .4rem 0 .1rem; }
.cl-sub { font-size: .74rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: .45rem 0 0; }
.cl-notes { margin: .2rem 0 0; padding-left: 1.1rem; }
.cl-notes li { color: var(--muted); font-size: .8rem; line-height: 1.45; margin: .1rem 0; }

/* erase lives in its own nested disclosure inside Advanced */
details.adv.danger-zone { margin-top: .3rem; }
details.adv.danger-zone summary { color: color-mix(in srgb, #ff5c5c 55%, var(--muted)); }
details.adv.danger-zone[open] summary { color: color-mix(in srgb, #ff5c5c 62%, var(--text)); }
.widebtn.danger { border-color: #a12626; color: color-mix(in srgb, #ff5c5c 62%, var(--text)); }
.widebtn.danger:hover { background: #a12626; color: #fff; }
.vtag { color: var(--muted); font-size: .78rem; }

/* menu bar */
.menubar {
  /* rem so the bar grows/shrinks with the global text size (2.5rem = 40px at
     the default 16px root) — keeps the top chrome usable from 20% to 300%. */
  height: 2.5rem; display: flex; align-items: center; gap: .75rem; padding: 0 1rem;
  background: var(--bar); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border); position: relative; z-index: 5;
}
.menubar .brand { font-weight: 800; background: linear-gradient(135deg,var(--accent),var(--accent2));
  -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.brand-btn { display: flex; align-items: center; gap: .3rem; background: none; border: 0;
  padding: .3rem .55rem; border-radius: .4rem; cursor: pointer; font-size: 1rem; }
.brand-btn:hover { background: rgba(255,255,255,.07); }
.brand-btn .caret { color: var(--muted); font-size: .7rem; }
.pill { font-variant-numeric: tabular-nums; color: var(--muted) !important; }
.menubar .crumbs { color: var(--muted); font-size: .85rem; }
.menubar .crumbs a { color: var(--accent); cursor: pointer; }
.menubar .spacer { flex: 1; }
.menubar button {
  padding: .3rem .8rem; border-radius: .4rem; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer; font-size: .8rem;
}
.menubar button:hover { border-color: var(--accent); box-shadow: 0 0 16px var(--glow); }
/* The fullscreen toggle is a GLYPH, not a word, and it sits in the far corner —
   last in the bar, past the named actions, the same right-hand cluster the app
   bar puts it in. Square padding so the icon centres, and the pressed state
   borrows the accent: the browser's own fullscreen chrome disappears, so the bar
   is the only thing left that can say which mode you're in. */
.menubar .fs-btn { display: inline-flex; align-items: center; justify-content: center;
  padding: .3rem .45rem; line-height: 0; color: var(--muted); }
.menubar .fs-btn:hover { color: var(--text); }
.menubar .fs-btn.on { color: var(--accent); border-color: var(--accent); }

/* arrange bar — stands IN PLACE of the menubar (same height, so #desktop's
   inset still lines up) while icons are unlocked. Phones jiggle their icons and
   never say what mode you're in; we say it, and we don't jiggle. */
.arrange-bar {
  height: 2.5rem; display: flex; align-items: center; gap: .6rem; padding: 0 1rem;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 32%, var(--bar)), color-mix(in srgb, var(--accent2) 24%, var(--bar)));
  backdrop-filter: blur(10px); border-bottom: 1px solid var(--accent);
  position: relative; z-index: 5; font-size: .85rem;
}
.arrange-bar .ab-glyph { font-size: 1rem; color: var(--accent); }
.arrange-bar .ab-msg { flex: 0 0 auto; font-weight: 600; white-space: nowrap; }
/* the how-to is the first thing to go when the bar is narrower than a phone */
.arrange-bar .ab-hint { min-width: 0; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 460px) { .arrange-bar .ab-hint { display: none; } }
.arrange-bar .spacer { flex: 1; }
.arrange-bar button {
  padding: .3rem 1.1rem; border-radius: .4rem; border: 1px solid var(--accent);
  background: var(--accent); color: var(--onaccent, #fff); cursor: pointer;
  font-size: .82rem; font-weight: 600;
}

/* desktop surface */
#desktop {
  position: absolute; inset: 2.5rem 0 0 0; /* matches the menubar height (scales with text size) */
  /* Both axes scroll: an icon dragged far right in landscape must stay reachable
     after rotating back to portrait (it sits beyond the viewport, so the surface
     has to scroll to it — same as icons dragged below the fold). */
  overflow: auto; overscroll-behavior: contain;
}
#desktop.dragover::after {
  content: 'Drop files to add them to your Home Screen';
  position: fixed; inset: calc(2.5rem + 1.2rem) 1.2rem 1.2rem; border: 2px dashed var(--accent); border-radius: 1rem;
  display: flex; align-items: center; justify-content: center; color: var(--accent);
  font-size: 1.1rem; background: rgba(123,92,255,.06); pointer-events: none; z-index: 4;
}
/* soft always-there reminder that the whole surface is a drop zone */
.drop-hint {
  position: fixed; bottom: .7rem; left: 50%; transform: translateX(-50%);
  color: var(--muted); opacity: .4; font-size: .74rem; pointer-events: none; z-index: 3;
  white-space: nowrap;
}
@media (pointer: coarse) { .drop-hint { display: none !important; } } /* no OS drag on phones */

/* icons — cell width follows the desktop's grid pitch (--cell, set by JS) so
   at least 5 icons fit across on phones without wasted whitespace */
.icon {
  position: absolute; width: calc(var(--cell, 104px) - 8px); display: flex; flex-direction: column; align-items: center;
  gap: .35rem; padding: .4rem .1rem; border-radius: .6rem; cursor: pointer; text-align: center;
  /* LOCKED by default: a finger that lands on an icon still pans the page, so
     scrolling a phone can't accidentally pick an icon up and post it into a
     folder. pinch-zoom stays allowed (accessibility); only double-tap zoom goes,
     which we want anyway — double-tap opens the app. */
  touch-action: pan-x pan-y pinch-zoom;
  -webkit-touch-callout: none; /* no iOS image callout on our long-press */
}
/* unlocked, but only inside Arrange mode — now the icon owns the gesture */
#desktop.arranging .icon { touch-action: none; }
.icon:hover { background: rgba(255,255,255,.05); }
.icon.selected { background: rgba(123,92,255,.18); outline: 1px solid var(--accent); }
/* Icons are transparent sticker GIFs — no box, no border: they float on the
   wallpaper with a soft drop shadow and a happy little hover pop. */
.icon .thumb {
  width: min(var(--isize, 64px), calc(var(--cell, 104px) - 16px));
  height: min(var(--isize, 64px), calc(var(--cell, 104px) - 16px));
  display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--isize, 64px) * .62);
}
.icon .thumb img {
  width: 100%; height: 100%; object-fit: contain; image-rendering: pixelated;
  /* The thumb's font-size is sized for emoji glyphs (62% of the icon), and an
     <img> that has not decoded yet renders its ALT TEXT at that size — a
     filename flashed at 40px on a normal icon and 119px on a large one. The
     alt is empty (the name is in the .label below), so this is the belt to
     that braces: nothing in a thumb may ever paint text at glyph size. */
  font-size: 10px; line-height: 1.1; overflow: hidden;
  filter: drop-shadow(0 4px 5px rgba(0, 0, 0, .45));
  transition: transform .16s ease;
}
.icon:hover .thumb img { transform: translateY(-3px) scale(1.07); }
.icon:active .thumb img { transform: translateY(0) scale(.96); }

/* the up-hole: the corner cell inside a folder is a hole back to the parent —
   click to climb out, or drop an icon in to send it up a level */
.icon.uphole { cursor: pointer; }
.uphole .hole {
  width: calc(var(--isize, 64px) * .84); height: calc(var(--isize, 64px) * .84);
  border: 2.5px dashed var(--muted); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--isize, 64px) * .42); color: var(--muted);
  background: radial-gradient(circle at 50% 42%, rgba(0, 0, 0, .5), rgba(0, 0, 0, .12));
  transition: transform .15s ease, border-color .15s, color .15s;
}
.uphole:hover .hole, .uphole.drop-target .hole {
  border-color: var(--accent); color: var(--accent); transform: scale(1.08);
}
.uphole .label { color: var(--muted); }
.icon .label {
  font-size: .78rem; max-width: calc(var(--cell, 104px) - 12px); word-break: break-word; line-height: 1.15;
  max-height: 2.4em; overflow: hidden;
  /* readable on ANY wallpaper the user picks (light or photo) */
  color: var(--label);
  text-shadow: 0 1px 2px var(--labelshadow), 0 0 6px var(--labelshadow);
}
.icon .label input {
  font: inherit; width: 88px; text-align: center; border: 1px solid var(--accent);
  border-radius: .3rem; background: var(--surface); color: var(--text);
}
.icon.drop-target .thumb { outline: 2px solid var(--accent2); }

/* provenance shield on signed GIFs (bottom-right of the thumbnail) */
.icon .thumb { position: relative; }
.sig-badge {
  position: absolute; right: -3px; bottom: -3px; width: 1.05rem; height: 1.05rem;
  display: flex; align-items: center; justify-content: center; border-radius: 50%;
  font-size: .68rem; font-weight: 800; color: #fff; border: 2px solid var(--bg);
  line-height: 1; pointer-events: none;
}
.sig-badge.sig-ok  { background: #34c759; }
.sig-badge.sig-bad { background: #ff5c5c; }
.sig-badge.sig-unk { background: #8888aa; }

/* THE FRESH PILL on apps that hold no data yet (bottom-left — opposite the
   shield). It reads "NEW", or on a SIGNED app the VERSION that arrived.
   Same slot, same colour, same disappearing act: a version is worth reading
   once, when a copy lands, and is noise from then on — so it lives exactly as
   long as "NEW" would have, and the nameplate keeps its whole width for the
   name. A version tag gets a hair more room and drops the wide tracking,
   because "v11.4.0" is a string to read, not a word to shout. */
.new-badge {
  position: absolute; left: -4px; bottom: -4px; pointer-events: none;
  font-size: .5rem; font-weight: 800; letter-spacing: .06em; line-height: 1;
  padding: .16rem .3rem; border-radius: .3rem; color: #fff; border: 2px solid var(--bg);
  background: var(--accent, #7b5cff);
  white-space: nowrap; max-width: calc(var(--cell, 104px) - 8px); overflow: hidden;
}
/* pointer-events stays NONE, exactly as for NEW: this pill sits on the corner
   of the thumb, and a hoverable badge there would swallow a drag that started
   on it. The version is on the nameplate's tooltip, which IS hoverable. */
.new-badge.isver { letter-spacing: 0; }

/* "SYS" tag on trusted system-app launchers (Meeting/Broadcast/App Store):
   honest signage that this icon opens a first-party page with powers the
   sandbox withholds from ordinary apps. Quiet on purpose: it sits in the
   NEW badge's corner (system launchers are never "fresh", so the slot is
   always free) with NEW's geometry but a MUTED slate instead of the accent —
   provenance signage, not a promotion. Tooltip still says which powers. */
.sysbadge {
  position: absolute; left: -4px; bottom: -4px; pointer-events: auto; cursor: help;
  font-size: .5rem; font-weight: 800; letter-spacing: .06em; line-height: 1;
  padding: .16rem .3rem; border-radius: .3rem; border: 2px solid var(--bg);
  background: #62627a; color: #e6e6f0;
}

/* Big red ✕ across a PROVIDER app anywhere outside the Providers folder
   (docs/providers.md): the OS only recognizes providers living directly in
   that folder, so out here the app is inert and the tile says so at full
   size — not a badge, a veto. The GIF underneath stays visible (it's intact
   and shareable); pointer-events on, so the tooltip explains the fix. */
.provider-x {
  position: absolute; inset: -4px; display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--isize, 64px) * 0.95); font-weight: 800; line-height: 1;
  color: #ff3b30; text-shadow: 0 0 6px rgba(0,0,0,.65), 0 0 2px rgba(0,0,0,.9);
  pointer-events: auto; cursor: help; user-select: none;
}

/* "MIRROR" band straight across the middle of a synced-mirror icon — a bright
   cyan ribbon (its own colour, distinct from NEW/SYSTEM/identity) that reads at
   a glance as "this copy tracks a master elsewhere". */
.mirror-ribbon {
  position: absolute; left: -6px; right: -6px; top: 50%; transform: translateY(-50%) rotate(-8deg);
  text-align: center; font-size: .5rem; font-weight: 800; letter-spacing: .14em; line-height: 1;
  text-transform: uppercase; padding: .16rem 0; pointer-events: none;
  color: #04252b; background: #22d3ee; /* dark ink on bright cyan */
  border-top: 1px solid rgba(255,255,255,.5); border-bottom: 1px solid rgba(0,0,0,.35);
  box-shadow: 0 2px 6px rgba(0,0,0,.45);
}

/* NAMEPLATE on a SIGNED app's tile: its own declared short name, IN PLACE OF
   THE FILENAME. It used to float over the top border of the animation while
   the filename sat underneath — the tile named the app twice, and one of the
   two names was written across the artwork. Now there is one name per tile:
   the signed one, in the label slot, off the picture. Deep navy, its own
   family, so provenance never reads as "system privilege". Only signed tiles
   get one (see desktop.js buildIcon); everything else shows its filename.

   IT WRAPS, IT DOES NOT CUT. A 104px cell fits about 13 characters on a line,
   and the first cut of this plate spent five of them on a version — which is
   how "Scanned PDF Tables" became "Scanned…". The version moved to the fresh
   pill, and what is left uses the whole width and a SECOND LINE before it
   gives up any letters. Two lines cost the grid nothing: rows are already tall
   enough for two lines of ordinary label text, which is larger than this.

   It may also run the full width of the tile. Bare text stops 4px short each
   side so a long filename stays off its neighbours; a plate is a solid box
   that never overlaps one, so it can have those 8px back.

   text-shadow is switched OFF — .label carries a wallpaper shadow meant for
   bare text, which smears letters against a plate. */
.nameplate {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  max-width: 100%; max-height: 2.9em; overflow: hidden; box-sizing: border-box;
  font-size: .6rem; font-weight: 700; letter-spacing: .01em; line-height: 1.25;
  padding: .16rem .34rem; border-radius: .3rem; cursor: help;
  overflow-wrap: anywhere; vertical-align: top; text-align: center;
  background: #0e1526; color: #e4ebf8; border: 1px solid #3b4d6b;
  box-shadow: 0 2px 6px rgba(0,0,0,.3); text-shadow: none;
}
.icon .label.plated {
  text-shadow: none; max-height: none; overflow: visible;
  max-width: calc(var(--cell, 104px) - 8px);
}

/* Passkey lock: the icon still plays; this says Open will ask. Top-right so
   it does not sit on the signature shield (bottom-right) or NEW (bottom-left). */
.lock-badge {
  position: absolute; right: -4px; top: -4px; pointer-events: none;
  width: 1.05rem; height: 1.05rem; display: flex; align-items: center; justify-content: center;
  border-radius: 50%; font-size: .62rem; line-height: 1;
  color: #fff; background: #3d3d55; border: 2px solid var(--bg);
}

/* context menu */
.ctx {
  position: absolute; z-index: 20; background: var(--surface); border: 1px solid var(--border);
  border-radius: .5rem; padding: .3rem; min-width: 150px; box-shadow: 0 12px 40px rgba(0,0,0,.5);
}
.ctx button {
  display: block; width: 100%; text-align: left; padding: .45rem .7rem; border: 0; border-radius: .35rem;
  background: none; color: var(--text); cursor: pointer; font-size: .85rem;
}
.ctx button:hover { background: rgba(123,92,255,.2); }
.ctx .sep { height: 1px; background: var(--border); margin: .3rem .2rem; }
.ctx button.danger { color: color-mix(in srgb, #ff5c5c 62%, var(--text)); }

/* modal */
.modal-bg {
  position: fixed; inset: 0; z-index: 30; background: rgba(0,0,0,.55);
  display: flex; align-items: center; justify-content: center;
}
.modal {
  background: var(--surface); border: 1px solid var(--border); border-radius: .8rem;
  padding: 1.6rem; text-align: center;
  /* Never wider or taller than the phone screen; scroll a tall body instead of
     spilling off-screen. .modal.wide overrides these for the settings sheet. */
  width: min(420px, calc(100vw - 24px));
  max-height: calc(100vh - 24px);
  max-height: calc(100dvh - 24px);
  overflow: auto;
}
/* The bar inside showBusy's modal — the same shape and the same indeterminate
   sweep the store uses, because it is the same promise: a phase with nothing
   honest to count MOVES rather than parking at a number it cannot justify. */
.busy-bar { height: .4rem; border-radius: 999px; background: var(--border); overflow: hidden; margin: 0 auto 1.2rem; width: 12rem; max-width: 100%; }
.busy-bar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .15s linear; }
.busy-bar.busy i { width: 100%; background: linear-gradient(90deg, transparent 0%, var(--accent) 50%, transparent 100%);
                   animation: busysweep 1.1s linear infinite; }
@keyframes busysweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .busy-bar.busy i { animation: none; opacity: .55; } }
.modal h3 { margin-bottom: .6rem; }
.modal p { color: var(--muted); font-size: .9rem; margin-bottom: 1.2rem; line-height: 1.45; }
.modal p a { color: var(--accent); }
.modal button {
  padding: .5rem 1.4rem; border-radius: .5rem; border: 1px solid var(--accent);
  background: var(--accent); color: var(--onaccent, #fff); cursor: pointer;
}
.modal.wide { max-width: 560px; width: 92vw; max-height: 86vh; overflow-y: auto; text-align: left; }
.modal.wide h3 { text-align: center; }
.modal.wide h4 { margin: .2rem 0 .5rem; font-size: .95rem; }
.add-actions { display: flex; gap: .5rem; margin-bottom: .4rem; }
.add-actions button { flex: 1; padding: .6rem; border-radius: .5rem; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer; }
.add-actions button:hover { border-color: var(--accent); }
.add-url-row { display: flex; gap: .5rem; margin-bottom: .4rem; }
.add-url-row input { flex: 1; min-width: 0; margin: 0; }
.add-url-row button { padding: .6rem 1rem; border-radius: .5rem; border: 1px solid var(--accent);
  background: var(--accent); color: var(--onaccent, #fff); cursor: pointer; }
.add-url-row button:disabled { opacity: .55; cursor: default; }
.add-sep { height: 1px; background: var(--border); margin: 1rem 0; }
.add-help { color: var(--muted); font-size: .82rem; line-height: 1.4; margin-bottom: .6rem; }
.add-help.bad { color: color-mix(in srgb, #ff5c5c 62%, var(--text)); }

/* settings: background picker row + collapsed Advanced section */
.bg-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .4rem; }
.modal.wide .bg-row input[type="color"], .bg-row input[type="color"] {
  width: 3.2rem; min-width: 3.2rem; flex: 0 0 auto; height: 2.2rem; padding: .15rem;
  border: 1px solid var(--border); border-radius: .4rem; background: var(--surface); cursor: pointer; }
.bg-row button { padding: .45rem .8rem; border-radius: .45rem; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer; font-size: .82rem; }
.bg-row button:hover { border-color: var(--accent); }
.ts-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.ts-row button { min-width: 2.6rem; padding: .45rem .7rem; border-radius: .45rem; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer; font-size: .9rem; }
.ts-row button:hover { border-color: var(--accent); }
.ts-row #ts-val { min-width: 3rem; text-align: center; color: var(--muted); }
.hist-list { max-height: 52vh; overflow-y: auto; margin: .4rem 0 .2rem; }
.hist-row { display: flex; align-items: center; gap: .5rem; padding: .4rem 0; border-bottom: 1px solid var(--border); }
.hist-open { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .1rem; cursor: pointer; text-decoration: none; }
.hist-name { color: var(--text); font-weight: 600; }
.hist-meta { color: var(--muted); font-size: .78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Beat the generic `.modal button` (accent-filled) so the row's ✕ stays subtle. */
details.adv summary { cursor: pointer; color: var(--muted); font-size: .85rem; padding: .3rem 0;
  user-select: none; }
details.adv summary:hover { color: var(--text); }
details.adv[open] summary { color: var(--text); margin-bottom: .4rem; }
details.adv h4 { margin-top: .8rem; }
.modal.wide textarea, .modal.wide input {
  width: 100%; box-sizing: border-box; padding: .55rem .7rem; border-radius: .5rem;
  border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; margin-bottom: .5rem;
}
.modal.wide textarea { resize: vertical; }
.modal.wide textarea.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .74rem; color: var(--muted); }
.modal.wide .widebtn { width: 100%; padding: .55rem; border-radius: .5rem; border: 1px solid var(--accent);
  background: var(--accent); color: var(--onaccent, #fff); cursor: pointer; margin-bottom: .3rem; }
#ad-create { background: var(--accent); border: 1px solid var(--accent); color: var(--onaccent, #fff); }
/* Breathing room between a dialog's message (or the section above) and its
   action buttons — without it the buttons feel cramped against the text. */
.modal-actions { display: flex; flex-direction: column; gap: .5rem; margin-top: 1rem; }
.modal-actions button { width: 100%; }
/* In the Settings sheet the actions sit right under the collapsed "Advanced
   settings" summary; add a clear separator on top of the breathing room. */
.modal.wide .modal-actions { margin-top: 1.1rem; padding-top: .95rem; border-top: 1px solid var(--border); }
.modal-actions button.danger { background: #a12626; border-color: #a12626; color: #fff; }
.modal-actions button.danger:hover { background: #c22e2e; }
.modal-actions button.ghost { background: none; border-color: var(--border); color: var(--muted); }
.hist-clearwrap { text-align: center; margin-top: .7rem; }
.hist-clear-link { color: var(--muted); font-size: .74rem; text-decoration: underline; cursor: pointer; opacity: .75; }
.hist-clear-link:hover { color: #e0574a; opacity: 1; }

/* Arrange mode: the desktop shows its bones. A phone HIDES its grid and wiggles
   the icons; we do the opposite — the pegboard appears so you can see the cells
   you're dropping into, and nothing wiggles. Its own element, not a background
   on #desktop, because applyBackground() owns that inline style (wallpaper). */
.pegboard {
  position: absolute; left: 0; top: 0; display: none; pointer-events: none; z-index: 0;
  background-image:
    repeating-linear-gradient(to right, var(--pegline, rgba(123,92,255,.28)) 0 1px, transparent 1px var(--cell, 104px)),
    repeating-linear-gradient(to bottom, var(--pegline, rgba(123,92,255,.28)) 0 1px, transparent 1px var(--row, 104px));
  background-position: 12px 12px; /* GRID.origin — cell edges, not the viewport */
}
#desktop.arranging .pegboard { display: block; }
#desktop.arranging { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 45%, transparent); }
/* the cell an icon would land in, tracked live while it's dragged */
.cell-ghost {
  position: absolute; display: none; pointer-events: none; z-index: 0;
  border: 2px solid var(--accent); border-radius: .6rem;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  box-shadow: 0 0 18px var(--glow);
}
#desktop.arranging .cell-ghost.on { display: block; }
/* the picked-up icon lifts on a shadow — a MOVE, not a jiggle (mouse drags too) */
.icon.lifted { z-index: 2; }
.icon.lifted .thumb img { transform: translateY(-6px) scale(1.14); filter: drop-shadow(0 12px 14px rgba(0,0,0,.6)); }

/* undo toast — the answer to "it fell into a folder and I don't know which" */
.toast {
  position: fixed; left: 50%; bottom: 1.2rem; transform: translateX(-50%);
  display: none; align-items: center; gap: .8rem; max-width: min(92vw, 30rem);
  padding: .55rem .7rem .55rem 1rem; border-radius: .6rem; z-index: 8;
  background: var(--surface); border: 1px solid var(--accent);
  box-shadow: 0 8px 30px rgba(0,0,0,.55); font-size: .84rem;
}
.toast.on { display: flex; }
.toast .toast-msg { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.toast button {
  flex: 0 0 auto; padding: .3rem .9rem; border-radius: .4rem; border: 1px solid var(--accent);
  background: none; color: var(--accent); cursor: pointer; font-size: .8rem; font-weight: 600;
}
.toast button:hover { background: rgba(123,92,255,.15); }

/* empty hint — fixed so it stays visible even though the surface scrolls */
.hint {
  position: fixed; bottom: 2.4rem; left: 50%; transform: translateX(-50%);
  color: var(--muted); font-size: .8rem; pointer-events: none; z-index: 3;
}

/* Settings → AI models */
.ai-row { border: 1px solid var(--border); border-radius: .55rem; padding: .55rem .6rem; margin-bottom: .55rem; }
.ai-head { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.ai-head b { flex: 1; font-size: .9rem; }
.ai-test { padding: .28rem .8rem; border-radius: .4rem; border: 1px solid var(--accent); background: var(--accent); color: var(--onaccent, #fff); cursor: pointer; font-size: .78rem; }
.ai-status { font-size: .78rem; color: var(--muted); min-width: 3rem; }
.ai-status.ok { color: color-mix(in srgb, #34c759 60%, var(--text)); }
.ai-status.bad { color: color-mix(in srgb, #ff5c5c 62%, var(--text)); }
.ai-2 { display: flex; gap: .4rem; }
/* Per-role source select: your own endpoint vs an installed Provider app */
.modal.wide .ai-row .ai-src { width: 100%; box-sizing: border-box; margin-bottom: .4rem; padding: .45rem .6rem;
  border-radius: .5rem; border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; font-size: .85rem; }
.modal.wide .ai-row input { margin-bottom: .4rem; }
.modal.wide .ai-row .ai-2 input { margin-bottom: 0; }

/* Settings → Third-party APIs (reuses .ai-row chrome) */
.modal.wide .api-name { flex: 1; min-width: 0; margin-bottom: 0; padding: .3rem .5rem; font-size: .88rem; font-weight: 600; }
.modal.wide .api-row select { width: 100%; box-sizing: border-box; padding: .55rem .7rem; border-radius: .5rem;
  border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; }
.modal.wide .api-row .ai-2 { align-items: stretch; }
.modal.wide .api-row .ai-2 select { flex: 1; }
.modal.wide .api-row .ai-2 .api-authname { flex: 1; }
.api-adv { margin-bottom: .4rem; }
.api-adv summary { cursor: pointer; color: var(--muted); font-size: .78rem; padding: .1rem 0; user-select: none; }
.modal.wide .api-row .api-adv .api-proxy-url { margin: .35rem 0 0; font-size: .82rem; }
.modal.wide .api-row .api-status { min-height: 1rem; font-size: .78rem; margin-bottom: 0; }

/* THE standard row-delete control — same class and trash glyph everywhere in
   GifOS (settings/invite rows here; lobby + pinned files in run.html).
   Borderless, inherits the row's muted color, danger-red on hover. */
button.row-del { flex: 0 0 auto; background: none; border: none; color: var(--muted); cursor: pointer; padding: .2rem .3rem; line-height: 0; border-radius: .35rem; }
button.row-del:hover { color: #e0574a; }
button.row-del svg { display: block; pointer-events: none; }

/* The build this page is running — a fact, not a control: muted, non-interactive,
   and never a tab stop. The version belongs in the page, not in the URL. */

/* Settings → Payments (paySectionHtml in desktop.js). A window onto the
   purse: per-app spend, entitlements, the per-charge ceiling. */
.pay-app { border: 1px solid var(--border); border-radius: .55rem; padding: .55rem .6rem; margin-bottom: .55rem; }
.pay-head { display: flex; justify-content: space-between; gap: .6rem; align-items: baseline; }
.pay-dim { color: var(--muted, #9a9ab5); font-size: .82rem; }
.pay-led-row { font-size: .85rem; margin-top: .3rem; }
.pay-ents { margin-top: .35rem; font-size: .85rem; }
.pay-ent { display: inline-flex; align-items: center; gap: .25rem; border: 1px solid var(--border); border-radius: .4rem; padding: .1rem .35rem; }
.pay-cap-row { margin-top: .45rem; font-size: .85rem; }
.pay-cap { width: 5.5rem; padding: .25rem .4rem; font: inherit; }
