{"body":"<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, interactive-widget=resizes-content\" />\n    <title>Walkable</title>\n    <link\n      rel=\"icon\"\n      type=\"image/svg+xml\"\n      href=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' fill='none'%3E%3Ccircle cx='31' cy='31' r='4.6' fill='%233F5233' opacity='.32'/%3E%3Ccircle cx='23.5' cy='25' r='4.8' fill='%233F5233' opacity='.55'/%3E%3Ccircle cx='16.5' cy='17.5' r='5' fill='%234D6040' opacity='.8'/%3E%3Ccircle cx='11.5' cy='9.5' r='5.2' fill='%23C4983A'/%3E%3Ccircle cx='9.6' cy='7.2' r='1.5' fill='%23FFF8E7' opacity='.9'/%3E%3C/svg%3E\"\n    />\n    <style>\n:root {\n  --bg: #f5f2ec;\n  --nav-bg: #1f2a2e;\n  --nav-text: #f3efe8;\n  --accent: #e07a5f;\n  --muted: #a9a39b;\n  --shadow: 0 10px 30px rgba(0, 0, 0, 0.12);\n  --menu-shadow: 0 14px 40px rgba(0, 0, 0, 0.28);\n  --nav-height: 56px;\n  font-family: \"Avenir Next\", \"Avenir\", \"Futura\", \"Segoe UI\", sans-serif;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  margin: 0;\n  background: var(--bg);\n  color: #1f2a2e;\n}\n\n.shell {\n  min-height: 100vh;\n  /* Mobile browsers size 100vh to the viewport with their toolbars hidden;\n     100dvh is the height actually visible right now (see #app-frame). */\n  min-height: 100dvh;\n  display: flex;\n  flex-direction: column;\n}\n\n/* ---- The frame ---- */\n\n.frame {\n  height: var(--nav-height);\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 0 12px 0 8px;\n  background: var(--nav-bg);\n  color: var(--nav-text);\n  box-shadow: var(--shadow);\n}\n\n.zone {\n  display: flex;\n  align-items: center;\n  min-width: 0;\n}\n\n.zone-left {\n  gap: 4px;\n  flex: 0 0 auto;\n}\n\n.zone-center {\n  flex: 1 1 auto;\n  min-width: 0;\n  justify-content: flex-start;\n}\n\n.zone-right {\n  flex: 0 0 auto;\n  justify-content: flex-end;\n  gap: 4px;\n}\n\n.dev-frame {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  max-width: min(42vw, 420px);\n  padding: 4px 5px 4px 9px;\n  border: 1px solid color-mix(in srgb, #f1c40f 65%, transparent);\n  border-radius: 999px;\n  background: color-mix(in srgb, #f1c40f 18%, var(--nav-bg));\n  color: #fff4bd;\n  font-size: 0.72rem;\n  font-weight: 650;\n}\n\n.dev-frame[hidden] {\n  display: none;\n}\n\n.dev-frame-label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.dev-frame-disconnect {\n  border: 0;\n  border-radius: 999px;\n  padding: 3px 7px;\n  background: rgba(255, 255, 255, 0.14);\n  color: inherit;\n  font: inherit;\n  cursor: pointer;\n}\n\n.dev-frame-disconnect:hover {\n  background: rgba(255, 255, 255, 0.24);\n}\n\n@media (max-width: 720px) {\n  .dev-frame-label {\n    max-width: 22vw;\n  }\n}\n\n/* ---- Left: waffle + wordmark ---- */\n\n.apps {\n  position: relative;\n}\n\n.waffle {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 36px;\n  height: 36px;\n  border-radius: 8px;\n  color: var(--nav-text);\n  text-decoration: none;\n  font-size: 1.2rem;\n  line-height: 1;\n}\n\n.waffle:hover,\n.waffle[aria-expanded=\"true\"] {\n  background: rgba(255, 255, 255, 0.1);\n}\n\n.wordmark {\n  font-weight: 700;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  font-size: 0.82rem;\n  color: var(--nav-text);\n  text-decoration: none;\n  white-space: nowrap;\n}\n\n/* ---- Center: the location sentence ---- */\n\n.lens {\n  position: relative;\n  display: flex;\n  min-width: 0;\n}\n\n.location {\n  display: inline-flex;\n  align-items: baseline;\n  gap: 8px;\n  min-width: 0;\n}\n\n/* The location is a <button> so it can open the lens (\"open with…\") menu on\n * lens routes; on plain app routes it stays disabled and looks like the old\n * inert sentence. */\n.lens-chip {\n  background: none;\n  border: 0;\n  padding: 4px 8px;\n  margin: -4px -8px;\n  font: inherit;\n  color: inherit;\n  border-radius: 8px;\n  /* Flex item of .lens: natural width normally, shrinkable (the app-name\n   * ellipsizes) when the frame gets tight. NOT max-width:100% — that resolves\n   * circularly against the shrink-wrapped parent and clamps the name. */\n  min-width: 0;\n}\n\n.lens-chip:disabled {\n  cursor: default;\n}\n\n.lens-chip:not(:disabled) {\n  cursor: pointer;\n}\n\n.lens-chip:not(:disabled):hover,\n.lens-chip[aria-expanded=\"true\"] {\n  background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.lens-caret {\n  font-size: 0.7rem;\n  align-self: center;\n}\n\n.lens-caret[hidden] {\n  display: none;\n}\n\n.app-icon {\n  display: inline-flex;\n  align-items: center;\n  align-self: center;\n  flex: 0 0 auto;\n  font-size: 1.1rem;\n  line-height: 1;\n}\n\n.app-icon[hidden] {\n  display: none;\n}\n\n.app-icon img {\n  display: block;\n  height: 1.15rem;\n  width: 1.15rem;\n  object-fit: contain;\n  border-radius: 4px;\n}\n\n.app-name {\n  font-size: 1.1rem;\n  font-weight: 600;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.from {\n  font-size: 0.85rem;\n  color: var(--muted);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  flex: 0 1 auto;\n}\n\n.from[hidden] {\n  display: none;\n}\n\n/* ---- Right: account chip + menu ---- */\n\n.account {\n  position: relative;\n}\n\n.account-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  background: transparent;\n  border: 1px solid transparent;\n  border-radius: 999px;\n  padding: 4px 10px 4px 4px;\n  color: var(--nav-text);\n  font: inherit;\n  font-size: 0.9rem;\n  cursor: pointer;\n  max-width: 220px;\n}\n\n.account-chip:hover,\n.account-chip[aria-expanded=\"true\"] {\n  background: rgba(255, 255, 255, 0.1);\n  border-color: rgba(255, 255, 255, 0.14);\n}\n\n.account[data-auth-state=\"signed-out\"] .account-chip {\n  border-color: rgba(255, 255, 255, 0.32);\n  padding: 6px 12px;\n  font-weight: 700;\n}\n\n.account[data-auth-state=\"signed-out\"] .account-chip:hover,\n.account[data-auth-state=\"signed-out\"] .account-chip:focus-visible {\n  background: rgba(255, 255, 255, 0.14);\n  border-color: rgba(255, 255, 255, 0.5);\n}\n\n.account[data-auth-state=\"signed-out\"] .avatar,\n.account[data-auth-state=\"signed-out\"] .caret {\n  display: none;\n}\n\n/* Persistent frame toggle for phantom users: shared synthetic accounts whose\n * pages are included or excluded by the user's private cred rating on each\n * host-provided phantom root. */\n.phantom-chip[hidden] {\n  display: none;\n}\n\n.phantom-chip {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 7px;\n  min-width: 100px;\n  height: 34px;\n  flex: 0 0 auto;\n  background: rgba(226, 226, 226, 0.1);\n  color: rgba(243, 239, 232, 0.62);\n  border: 1px solid rgba(243, 239, 232, 0.22);\n  border-radius: 999px;\n  padding: 0 13px;\n  font: inherit;\n  font-size: 0.78rem;\n  font-weight: 800;\n  text-transform: uppercase;\n  cursor: pointer;\n  opacity: 0.78;\n  position: relative;\n  transition: background 120ms ease, border-color 120ms ease, color 120ms ease, opacity 120ms ease;\n}\n\n.phantom-chip[data-state=\"on\"] {\n  background: #b83f35;\n  color: #fff8f6;\n  border-color: rgba(255, 198, 185, 0.9);\n  opacity: 1;\n}\n\n.phantom-chip[data-busy=\"true\"] {\n  cursor: wait;\n  opacity: 0.68;\n}\n\n.phantom-chip[data-busy=\"true\"] .phantom-chip-short,\n.phantom-chip[data-busy=\"true\"] .phantom-chip-label {\n  visibility: hidden;\n}\n\n.phantom-chip::after {\n  content: \"\";\n  display: none;\n  position: absolute;\n  width: 14px;\n  height: 14px;\n  border: 2px solid currentColor;\n  border-right-color: transparent;\n  border-radius: 999px;\n}\n\n.phantom-chip[data-busy=\"true\"]::after {\n  display: block;\n  animation: phantom-spin 700ms linear infinite;\n}\n\n@keyframes phantom-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .phantom-chip[data-busy=\"true\"]::after {\n    animation: none;\n  }\n}\n\n.phantom-chip:hover,\n.phantom-chip:focus-visible {\n  background: rgba(226, 226, 226, 0.18);\n  border-color: rgba(243, 239, 232, 0.36);\n  color: rgba(243, 239, 232, 0.84);\n}\n\n.phantom-chip[data-state=\"on\"]:hover,\n.phantom-chip[data-state=\"on\"]:focus-visible {\n  background: #cb4b3f;\n  border-color: rgba(255, 220, 212, 0.95);\n  color: #fff;\n}\n\n.phantom-chip-short {\n  display: none;\n  letter-spacing: 0;\n}\n\n.phantom-chip-label {\n  white-space: nowrap;\n}\n\n/* The Phantoms chip's positioning context. */\n.phantom {\n  position: relative;\n  display: inline-flex;\n}\n\n.modal-backdrop {\n  position: fixed;\n  inset: 0;\n  z-index: 100;\n  display: grid;\n  place-items: center;\n  padding: 18px;\n  background: rgba(16, 21, 23, 0.54);\n}\n\n.modal-backdrop[hidden] {\n  display: none;\n}\n\n.phantom-confirm-modal {\n  width: min(420px, 100%);\n  border-radius: 12px;\n  background: #fff;\n  color: #1f2a2e;\n  box-shadow: var(--menu-shadow);\n  padding: 18px;\n  display: grid;\n  gap: 12px;\n}\n\n.phantom-confirm-modal h2 {\n  margin: 0;\n  font-size: 1.12rem;\n  line-height: 1.25;\n}\n\n.phantom-confirm-modal p {\n  margin: 0;\n  color: #5f676b;\n  line-height: 1.45;\n}\n\n.phantom-confirm-body {\n  display: grid;\n  gap: 10px;\n}\n\n/* The toggle word (\"visible\" / \"invisible\") in the modal title, strongly\n   highlighted so the new state reads at a glance. */\n.phantom-confirm-state {\n  font-weight: 800;\n  color: #173a2a;\n  background: #ffe39a;\n  padding: 0 0.28em;\n  border-radius: 4px;\n}\n\n/* The collapsed \"more…\" detail under the lead paragraph. */\n.phantom-confirm-more > summary {\n  cursor: pointer;\n  color: #2e7d5b;\n  font-weight: 600;\n  list-style: none;\n}\n\n.phantom-confirm-more > summary::-webkit-details-marker {\n  display: none;\n}\n\n.phantom-confirm-more[open] > summary {\n  margin-bottom: 8px;\n}\n\n/* `.phantom-confirm-modal p` zeroes margins and only the body's direct children\n   get the grid gap, so stacked paragraphs (the two inside the disclosure) would\n   otherwise run together. Restore the break between consecutive paragraphs. */\n.phantom-confirm-body p + p {\n  margin-top: 0.7em;\n}\n\n.phantom-confirm-actions {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  flex-wrap: wrap;\n}\n\n.phantom-confirm-dismiss {\n  display: inline-flex;\n  align-items: center;\n  gap: 7px;\n  font-size: 0.9rem;\n  color: #5f676b;\n  cursor: pointer;\n}\n\n.phantom-confirm-dismiss input {\n  width: 15px;\n  height: 15px;\n  accent-color: #2e7d5b;\n  cursor: pointer;\n}\n\n.phantom-confirm-action {\n  min-height: 36px;\n  border-radius: 8px;\n  padding: 0 13px;\n  font: inherit;\n  font-weight: 700;\n  cursor: pointer;\n  border: 1px solid #1f2a2e;\n  background: #1f2a2e;\n  color: #fff;\n}\n\n.frame-extension-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 7px;\n  position: relative;\n  min-width: 34px;\n  height: 34px;\n  flex: 0 0 auto;\n  border: 1px solid rgba(185, 213, 188, 0.42);\n  border-radius: 999px;\n  padding: 0 12px;\n  background: rgba(106, 143, 112, 0.22);\n  color: rgba(244, 250, 241, 0.9);\n  font: inherit;\n  font-size: 0.8rem;\n  font-weight: 800;\n  cursor: pointer;\n  transition: background 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;\n}\n\n.frame-extension-button[hidden] {\n  display: none;\n}\n\n.frame-extension-label {\n  white-space: nowrap;\n}\n\n.frame-extension-icon,\n.frame-extension-icon svg {\n  width: 18px;\n  height: 18px;\n}\n\n.frame-extension-icon {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  color: rgba(218, 240, 214, 0.95);\n}\n\n.frame-extension-icon svg {\n  display: block;\n}\n\n.frame-extension-icon svg {\n  fill: none;\n  stroke: currentColor;\n  stroke-width: 1.9;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n.frame-extension-badge {\n  position: absolute;\n  top: -4px;\n  right: -4px;\n  min-width: 18px;\n  height: 18px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border: 2px solid var(--nav-bg);\n  border-radius: 999px;\n  padding: 0 4px;\n  background: #8f3d2d;\n  color: #fff;\n  font-size: 0.66rem;\n  font-weight: 800;\n  line-height: 1;\n}\n\n.frame-extension-button:hover,\n.frame-extension-button:focus-visible {\n  background: rgba(126, 162, 130, 0.34);\n  border-color: rgba(214, 237, 209, 0.72);\n  color: #fff;\n}\n\n.frame-extension-button:focus-visible {\n  outline: 2px solid rgba(214, 237, 209, 0.55);\n  outline-offset: 2px;\n}\n\n.account-name {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.caret {\n  font-size: 0.7rem;\n  opacity: 0.7;\n}\n\n.avatar {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 28px;\n  height: 28px;\n  flex: 0 0 28px;\n  border-radius: 50%;\n  background: var(--accent);\n  color: #fff;\n  font-size: 0.85rem;\n  font-weight: 700;\n  text-transform: uppercase;\n  background-size: cover;\n  background-position: center;\n}\n\n.avatar.lg {\n  width: 40px;\n  height: 40px;\n  flex-basis: 40px;\n  font-size: 1.1rem;\n}\n\n/* ---- Frame dropdowns ---- */\n\n.apps-menu,\n.account-menu,\n.lens-menu {\n  position: absolute;\n  top: calc(100% + 8px);\n  min-width: 260px;\n  max-width: 320px;\n  background: #fff;\n  color: #1f2a2e;\n  border-radius: 12px;\n  box-shadow: var(--menu-shadow);\n  padding: 8px;\n  z-index: 50;\n}\n\n.apps-menu {\n  left: 0;\n  max-height: min(70vh, 520px);\n  overflow-y: auto;\n}\n\n.account-menu {\n  right: 0;\n}\n\n.lens-menu {\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n.apps-menu[hidden],\n.account-menu[hidden],\n.lens-menu[hidden] {\n  display: none;\n}\n\n.account-active {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 10px 12px;\n}\n\n.account-active-text {\n  min-width: 0;\n}\n\n.account-active-name {\n  font-weight: 600;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.account-active-sub {\n  font-size: 0.8rem;\n  color: #6b6b6b;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.menu-sep {\n  height: 1px;\n  background: #ececec;\n  margin: 6px 4px;\n}\n\n.menu-label {\n  font-size: 0.7rem;\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n  color: #9a9a9a;\n  padding: 4px 12px;\n}\n\n.menu-item {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  width: 100%;\n  padding: 9px 12px;\n  border: none;\n  border-radius: 8px;\n  background: transparent;\n  color: inherit;\n  font: inherit;\n  font-size: 0.9rem;\n  text-align: left;\n  text-decoration: none;\n  cursor: pointer;\n}\n\n.menu-item:hover {\n  background: #f3f0ea;\n}\n\n.menu-item[aria-current=\"page\"] {\n  background: #f3f0ea;\n  font-weight: 600;\n}\n\n.menu-item .avatar {\n  width: 26px;\n  height: 26px;\n  flex-basis: 26px;\n  font-size: 0.8rem;\n}\n\n.menu-item .menu-item-text {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.menu-item .menu-glyph {\n  display: inline-flex;\n  width: 26px;\n  justify-content: center;\n  opacity: 0.7;\n}\n\n.menu-item.danger {\n  color: #b3261e;\n}\n\n/* \"Coming soon\" rows: visible roadmap, dimmed and non-interactive. */\n.menu-item.is-soon {\n  cursor: default;\n  opacity: 0.5;\n}\n\n.menu-item.is-soon:hover {\n  background: transparent;\n}\n\n.menu-soon-tag {\n  margin-left: auto;\n  flex: 0 0 auto;\n  font-size: 0.62rem;\n  letter-spacing: 0.05em;\n  text-transform: uppercase;\n  color: #8a8a8a;\n  border: 1px solid #e0e0e0;\n  border-radius: 999px;\n  padding: 1px 7px;\n}\n\n/* A switchable account paired with its \"open in a new window\" action.\n   The switch button flex-grows; the icon stays a fixed icon button (#342). */\n.account-other-row {\n  display: flex;\n  align-items: center;\n}\n\n.account-other-row .account-other {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.account-new-window {\n  flex: 0 0 auto;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 30px;\n  height: 30px;\n  margin-right: 6px;\n  border: none;\n  border-radius: 8px;\n  background: transparent;\n  color: inherit;\n  font: inherit;\n  font-size: 0.95rem;\n  opacity: 0.55;\n  cursor: pointer;\n}\n\n.account-new-window:hover {\n  background: #f3f0ea;\n  opacity: 1;\n}\n\n/* Shared role accounts: \"act as a role\" rows + the acting-as band. A role wears\n   a distinct marker so a borrowed identity is never mistaken for one's own. */\n.avatar.role {\n  background: #2f6f6a;\n  color: #fff;\n}\n\n.account-role .menu-item-text {\n  font-style: italic;\n}\n\n/* When the account chip / menu is currently acting as a role, tint its avatar. */\n.account[data-acting-as-role=\"true\"] .avatar {\n  box-shadow: 0 0 0 2px #2f6f6a;\n}\n\n.account-acting-band {\n  padding: 8px 12px;\n  background: #eef5f4;\n  border-radius: 8px;\n  margin: 4px 8px;\n}\n\n.account-acting-note {\n  font-size: 0.8rem;\n  opacity: 0.8;\n  margin-bottom: 6px;\n}\n\n.account-switch-back {\n  color: #2f6f6a;\n  font-weight: 600;\n}\n\n:root[data-theme=\"dark\"] .account-acting-band {\n  background: #1c2b2a;\n}\n\n/* A teaching-band row: primary item + trailing help link. */\n.menu-row {\n  display: flex;\n  align-items: center;\n  gap: 2px;\n}\n\n.menu-row > .menu-item {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.menu-info {\n  flex: 0 0 auto;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 30px;\n  height: 30px;\n  border-radius: 8px;\n  color: #9a9a9a;\n  text-decoration: none;\n  font-size: 0.95rem;\n}\n\n.menu-info:hover {\n  background: #f3f0ea;\n  color: #1f2a2e;\n}\n\n.primary-button,\n.secondary-button {\n  min-width: 72px;\n  height: 36px;\n  border-radius: 8px;\n  font: inherit;\n  font-size: 0.9rem;\n  cursor: pointer;\n}\n\n.primary-button {\n  border: 1px solid #1f2a2e;\n  background: #1f2a2e;\n  color: #fff;\n}\n\n.secondary-button {\n  border: 1px solid #d7d2ca;\n  background: #fff;\n  color: #1f2a2e;\n}\n\n.primary-button:disabled,\n.secondary-button:disabled {\n  cursor: default;\n  opacity: 0.55;\n}\n\n/* ---- Content ---- */\n\n.content {\n  flex: 1;\n  min-height: 0;\n  position: relative;\n}\n\n/* ---- Public (chromeless) mode ----\n   Public read-only sites (docs/decisions/public-read-only-sites.md) hide the frame\n   for anonymous visitors and offer Login as a slim footer link. The server sets\n   <html data-chrome=\"public\"> in <head> so the frame never flashes pre-paint; the\n   shell sets data-signed-in once auth resolves, restoring full chrome for a\n   signed-in curator. Inert (footer stays display:none) outside public mode. */\n.public-footer {\n  display: none;\n}\nhtml[data-chrome=\"public\"] .frame {\n  display: none;\n}\nhtml[data-chrome=\"public\"] .public-footer {\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  padding: 10px 16px;\n  border-top: 1px solid rgba(31, 42, 46, 0.1);\n  background: var(--bg);\n}\nhtml[data-chrome=\"public\"][data-signed-in=\"true\"] .frame {\n  display: flex;\n}\nhtml[data-chrome=\"public\"][data-signed-in=\"true\"] .public-footer {\n  display: none;\n}\n.public-footer-link {\n  color: var(--muted);\n  font-size: 13px;\n  text-decoration: none;\n}\n.public-footer-link:hover {\n  color: #1f2a2e;\n  text-decoration: underline;\n}\n\n.app-error-banner {\n  position: absolute;\n  z-index: 30;\n  top: 12px;\n  left: 12px;\n  right: 12px;\n  max-width: 880px;\n  margin: 0 auto;\n  display: flex;\n  align-items: flex-start;\n  gap: 14px;\n  padding: 12px;\n  border: 1px solid #d78a5c;\n  border-radius: 10px;\n  background: #fff8f0;\n  color: #31251d;\n  box-shadow: 0 12px 30px rgba(31, 42, 46, 0.18);\n}\n\n/* Trust referral, not a prompt: a quiet note that a shared link asked for a\n * below-horizon lens, pointing at TrustLamp where an informed decision is\n * possible (decisions/lens-switcher-network-tier.md). Deliberately calm —\n * neutral surface, no warning colors: nothing is wrong, something is hidden. */\n.lens-referral-banner {\n  position: absolute;\n  z-index: 29;\n  top: 12px;\n  left: 12px;\n  right: 12px;\n  max-width: 880px;\n  margin: 0 auto;\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 14px;\n  border: 1px solid #d5d5d0;\n  border-radius: 10px;\n  background: #fbfbf8;\n  color: #31353a;\n  font-size: 0.9rem;\n  box-shadow: 0 8px 22px rgba(31, 42, 46, 0.12);\n}\n\n.lens-referral-banner[hidden] {\n  display: none;\n}\n\n.lens-referral-text {\n  min-width: 0;\n}\n\n.lens-referral-link {\n  white-space: nowrap;\n  color: inherit;\n  font-weight: 600;\n}\n\n.lens-referral-dismiss {\n  margin-left: auto;\n  background: none;\n  border: 0;\n  cursor: pointer;\n  color: inherit;\n  font-size: 0.9rem;\n  padding: 4px;\n}\n\n:root[data-theme=\"dark\"] .lens-referral-banner {\n  background: var(--surface);\n  color: var(--on-surface);\n  border-color: rgba(255, 255, 255, 0.16);\n}\n\n/* Mirror-on-demand fetch progress: shown while the host caches a publisher app\n   the user just opened (decision app-references-are-urls-invisible-cache §4). */\n.mirror-fetch-banner {\n  position: absolute;\n  z-index: 29;\n  top: 12px;\n  left: 12px;\n  right: 12px;\n  max-width: 880px;\n  margin: 0 auto;\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 14px;\n  border: 1px solid #d5d5d0;\n  border-radius: 10px;\n  background: #fbfbf8;\n  color: #31353a;\n  font-size: 0.9rem;\n  box-shadow: 0 8px 22px rgba(31, 42, 46, 0.12);\n}\n\n.mirror-fetch-banner[hidden] {\n  display: none;\n}\n\n.mirror-fetch-spinner {\n  flex: 0 0 auto;\n  width: 14px;\n  height: 14px;\n  border: 2px solid #d5d5d0;\n  border-top-color: #31353a;\n  border-radius: 50%;\n  animation: mirror-fetch-spin 0.9s linear infinite;\n}\n\n.mirror-fetch-banner.mirror-fetch-error .mirror-fetch-spinner {\n  display: none;\n}\n\n@keyframes mirror-fetch-spin {\n  to { transform: rotate(360deg); }\n}\n\n:root[data-theme=\"dark\"] .mirror-fetch-banner {\n  background: var(--surface);\n  color: var(--on-surface);\n  border-color: rgba(255, 255, 255, 0.16);\n}\n\n:root[data-theme=\"dark\"] .mirror-fetch-spinner {\n  border-color: rgba(255, 255, 255, 0.24);\n  border-top-color: var(--on-surface);\n}\n\n.app-error-banner[hidden] {\n  display: none;\n}\n\n.app-error-copy {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.app-error-title {\n  font-weight: 800;\n  font-size: 0.95rem;\n  line-height: 1.25;\n}\n\n.app-error-message {\n  margin-top: 3px;\n  color: #5f4937;\n  font-size: 0.88rem;\n  line-height: 1.35;\n}\n\n.app-error-details {\n  margin-top: 6px;\n  color: #71563e;\n  font-size: 0.78rem;\n}\n\n.app-error-details summary {\n  width: fit-content;\n  cursor: pointer;\n  font-weight: 700;\n}\n\n.app-error-details p {\n  margin: 5px 0 0;\n  overflow-wrap: anywhere;\n}\n\n.app-error-actions {\n  display: flex;\n  flex: 0 0 auto;\n  gap: 8px;\n  align-items: center;\n}\n\n.app-error-reload,\n.app-error-dismiss {\n  min-height: 34px;\n  border-radius: 8px;\n  padding: 0 11px;\n  font: inherit;\n  font-size: 0.82rem;\n  font-weight: 800;\n  cursor: pointer;\n  white-space: nowrap;\n}\n\n.app-error-reload {\n  border: 1px solid #1f2a2e;\n  background: #1f2a2e;\n  color: #fff;\n}\n\n.app-error-dismiss {\n  border: 1px solid #d9b98e;\n  background: #fff;\n  color: #5f4937;\n}\n\n#app-frame {\n  width: 100%;\n  height: calc(100vh - var(--nav-height));\n  /* 100vh is the *largest* viewport on mobile (toolbars retracted), so on a\n     phone the bottom of the frame sat under the browser's own toolbars, and\n     apps pinned to the bottom (e.g. the Chat composer) were unreachable:\n     touches inside the iframe never scroll the shell to reveal it. The\n     dynamic viewport tracks the toolbars (and, with interactive-widget=\n     resizes-content in index.html, the on-screen keyboard on Android; iOS\n     Safari ignores that and pans to the focused field instead). */\n  height: calc(100dvh - var(--nav-height));\n  border: none;\n  display: block;\n  background: #fff;\n}\n\n/* ---- Mobile: stay a single row; shed the least-needed bits first ---- */\n\n@media (max-width: 600px) {\n  .wordmark {\n    display: none;\n  }\n  .account-name,\n  .caret {\n    display: none;\n  }\n  .account-chip {\n    padding: 4px;\n  }\n  .phantom-chip {\n    min-width: 34px;\n    width: 34px;\n    padding: 0;\n  }\n  .phantom-chip-short {\n    display: inline;\n  }\n  .phantom-chip-label {\n    display: none;\n  }\n  .frame-extension-label {\n    display: none;\n  }\n  .frame-extension-button {\n    width: 34px;\n    padding: 0;\n  }\n  .app-name {\n    font-size: 1rem;\n  }\n  .app-error-banner {\n    top: 8px;\n    left: 8px;\n    right: 8px;\n    flex-direction: column;\n    gap: 10px;\n  }\n  .app-error-actions {\n    width: 100%;\n  }\n  .app-error-reload,\n  .app-error-dismiss {\n    flex: 1 1 0;\n  }\n}\n\n/* ---- Dark theme ----\n   Applied when the shell sets <html data-theme=\"dark\"> from the reader's account\n   preference (app.ts applyTheme; pre-painted by the inline <head> script). The nav\n   bar is already dark in both themes; these rules darken the remaining light\n   surfaces — the popover menus, the Phantoms modal, buttons, the app-load backdrop,\n   and the data-error banner — so the whole shell, including the modal, follows the\n   setting. Light mode is the default and left untouched. */\n:root[data-theme=\"dark\"] {\n  --bg: #15191b;\n  --muted: #8b9398;\n  --shadow: 0 10px 30px rgba(0, 0, 0, 0.45);\n  --menu-shadow: 0 14px 40px rgba(0, 0, 0, 0.6);\n  --surface: #1f262a;\n  --surface-2: #283034;\n  --on-surface: #e9eef0;\n  --on-surface-muted: #9aa3a8;\n  --border-soft: #323b40;\n  --accent-green: #2e7d5b;\n  --accent-green-border: #3c8f6a;\n}\n\n:root[data-theme=\"dark\"] body {\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .apps-menu,\n:root[data-theme=\"dark\"] .account-menu,\n:root[data-theme=\"dark\"] .lens-menu {\n  background: var(--surface);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .account-active-sub,\n:root[data-theme=\"dark\"] .menu-label,\n:root[data-theme=\"dark\"] .menu-info {\n  color: var(--on-surface-muted);\n}\n\n:root[data-theme=\"dark\"] .menu-sep {\n  background: var(--border-soft);\n}\n\n:root[data-theme=\"dark\"] .menu-item:hover,\n:root[data-theme=\"dark\"] .menu-item[aria-current=\"page\"],\n:root[data-theme=\"dark\"] .account-new-window:hover {\n  background: var(--surface-2);\n}\n\n:root[data-theme=\"dark\"] .menu-info:hover {\n  background: var(--surface-2);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .menu-item.danger {\n  color: #f08a82;\n}\n\n:root[data-theme=\"dark\"] .menu-soon-tag {\n  color: var(--on-surface-muted);\n  border-color: var(--border-soft);\n}\n\n:root[data-theme=\"dark\"] .secondary-button {\n  background: var(--surface-2);\n  border-color: var(--border-soft);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .primary-button {\n  border-color: var(--accent-green-border);\n  background: var(--accent-green);\n  color: #fff;\n}\n\n/* The Phantoms modal follows the theme. */\n:root[data-theme=\"dark\"] .phantom-confirm-modal {\n  background: var(--surface);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-modal p {\n  color: var(--on-surface-muted);\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-more > summary {\n  color: #6fc69b;\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-dismiss {\n  color: var(--on-surface-muted);\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-action {\n  border-color: var(--accent-green-border);\n  background: var(--accent-green);\n  color: #fff;\n}\n\n/* The toggle-state highlight stays a warm chip; darken its text for contrast. */\n:root[data-theme=\"dark\"] .phantom-confirm-state {\n  color: #1a2e22;\n}\n\n:root[data-theme=\"dark\"] #app-frame {\n  background: var(--bg);\n}\n\n/* Data-error banner: warm-dark amber instead of cream. */\n:root[data-theme=\"dark\"] .app-error-banner {\n  background: #2a211a;\n  border-color: #8a5a36;\n  color: #f0e3d6;\n}\n\n:root[data-theme=\"dark\"] .app-error-message {\n  color: #d8c2ab;\n}\n\n:root[data-theme=\"dark\"] .app-error-details {\n  color: #c9b196;\n}\n\n:root[data-theme=\"dark\"] .app-error-reload {\n  border-color: var(--accent-green-border);\n  background: var(--accent-green);\n}\n\n:root[data-theme=\"dark\"] .app-error-dismiss {\n  background: var(--surface-2);\n  border-color: #8a5a36;\n  color: #f0e3d6;\n}\n\n:root[data-theme=\"dark\"] .public-footer-link:hover {\n  color: var(--on-surface);\n}\n\n</style><script>\n      // Pre-paint theme: apply the cached resolved scheme (or the OS preference) so a\n      // returning reader's dark mode never flashes light before app.js loads the\n      // authoritative account preference and reconciles. Mirror of THEME_PAINT_HINT_KEY\n      // / applyTheme in app.ts.\n      (function () {\n        try {\n          var hint = localStorage.getItem(\"wkbl.themeResolved\");\n          var resolved =\n            hint === \"light\" || hint === \"dark\"\n              ? hint\n              : window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n                ? \"dark\"\n                : \"light\";\n          document.documentElement.dataset.theme = resolved;\n        } catch (e) {}\n      })();\n    </script>\n  </head>\n  <body>\n    <div class=\"shell\">\n      <header class=\"frame\">\n        <div class=\"zone zone-left\">\n          <div class=\"apps\" id=\"apps\">\n            <a\n              class=\"waffle\"\n              id=\"waffle\"\n              href=\"/\"\n              title=\"Apps\"\n              aria-label=\"Apps\"\n              aria-haspopup=\"menu\"\n              aria-expanded=\"false\"\n              aria-controls=\"apps-menu\"\n            >\n              <span aria-hidden=\"true\">☰</span>\n            </a>\n            <div class=\"apps-menu\" id=\"apps-menu\" role=\"menu\" hidden></div>\n          </div>\n          <a class=\"wordmark\" id=\"home-link\" href=\"/\">Walkable</a>\n        </div>\n\n        <div class=\"zone zone-center\">\n          <div class=\"lens\" id=\"lens\">\n            <button\n              class=\"location lens-chip\"\n              id=\"lens-chip\"\n              type=\"button\"\n              disabled\n              aria-haspopup=\"menu\"\n              aria-expanded=\"false\"\n              aria-controls=\"lens-menu\"\n            >\n              <span class=\"app-icon\" id=\"app-icon\" aria-hidden=\"true\" hidden></span>\n              <span class=\"app-name\" id=\"app-name\">App</span>\n              <span class=\"from\" id=\"from-owner\" hidden></span>\n              <span class=\"caret lens-caret\" id=\"lens-caret\" aria-hidden=\"true\" hidden>▾</span>\n            </button>\n            <div class=\"lens-menu\" id=\"lens-menu\" role=\"menu\" hidden></div>\n          </div>\n        </div>\n\n        <div class=\"zone zone-right\">\n          <div class=\"dev-frame\" id=\"dev-frame\" hidden>\n            <span class=\"dev-frame-label\" id=\"dev-frame-label\"></span>\n            <button class=\"dev-frame-disconnect\" id=\"dev-frame-disconnect\" type=\"button\">Disconnect</button>\n          </div>\n          <div class=\"phantom\" id=\"phantom\">\n            <button\n              class=\"phantom-chip\"\n              id=\"phantom-chip\"\n              type=\"button\"\n              hidden\n              title=\"Phantom users are hidden\"\n              aria-pressed=\"false\"\n              data-state=\"off\"\n            >\n              <span class=\"phantom-chip-short\" aria-hidden=\"true\">Ph</span>\n              <span class=\"phantom-chip-label\">Phantoms</span>\n            </button>\n          </div>\n          <div class=\"account\" id=\"account\" data-auth-state=\"signed-out\">\n            <button\n              class=\"account-chip\"\n              id=\"account-chip\"\n              aria-haspopup=\"false\"\n              aria-expanded=\"false\"\n              aria-label=\"Log in or sign up\"\n            >\n              <span class=\"avatar\" id=\"account-avatar\" aria-hidden=\"true\">·</span>\n              <span class=\"account-name\" id=\"user-label\">Log in / Sign up</span>\n              <span class=\"caret\" aria-hidden=\"true\">▾</span>\n            </button>\n            <div class=\"account-menu\" id=\"account-menu\" role=\"menu\" hidden></div>\n          </div>\n        </div>\n      </header>\n\n      <main class=\"content\">\n        <div class=\"lens-referral-banner\" id=\"lens-referral-banner\" role=\"status\" aria-live=\"polite\" hidden>\n          <span class=\"lens-referral-text\" id=\"lens-referral-text\"></span>\n          <a class=\"lens-referral-link\" id=\"lens-referral-link\" href=\"/apps/app-trustlamp\">See who trusts them</a>\n          <button class=\"lens-referral-dismiss\" id=\"lens-referral-dismiss\" type=\"button\" aria-label=\"Dismiss\">✕</button>\n        </div>\n        <div class=\"mirror-fetch-banner\" id=\"mirror-fetch-banner\" role=\"status\" aria-live=\"polite\" hidden>\n          <span class=\"mirror-fetch-spinner\" id=\"mirror-fetch-spinner\" aria-hidden=\"true\"></span>\n          <span class=\"mirror-fetch-text\" id=\"mirror-fetch-text\"></span>\n        </div>\n        <div class=\"app-error-banner\" id=\"app-error-banner\" role=\"status\" aria-live=\"polite\" hidden>\n          <div class=\"app-error-copy\">\n            <div class=\"app-error-title\" id=\"app-error-title\">This app could not load data</div>\n            <div class=\"app-error-message\" id=\"app-error-message\"></div>\n            <details class=\"app-error-details\">\n              <summary>Details</summary>\n              <p id=\"app-error-detail\"></p>\n            </details>\n          </div>\n          <div class=\"app-error-actions\">\n            <button class=\"app-error-reload\" id=\"app-error-reload\" type=\"button\">Reload app</button>\n            <button class=\"app-error-dismiss\" id=\"app-error-dismiss\" type=\"button\" aria-label=\"Dismiss app data warning\">Dismiss</button>\n          </div>\n        </div>\n        <iframe\n          id=\"app-frame\"\n          referrerpolicy=\"no-referrer\"\n          sandbox=\"allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation\"\n          title=\"App\"\n        ></iframe>\n      </main>\n\n      <!-- Public read-only sites: the only chrome an anonymous visitor sees. Hidden\n           by default; shown by CSS only under <html data-chrome=\"public\"> while\n           signed out (see docs/decisions/public-read-only-sites.md). -->\n      <footer class=\"public-footer\" id=\"public-footer\">\n        <a class=\"public-footer-link\" id=\"public-login-link\" href=\"/login\">Log in</a>\n      </footer>\n\n      <div class=\"modal-backdrop\" id=\"phantom-confirm-backdrop\" hidden>\n        <section\n          class=\"phantom-confirm-modal\"\n          role=\"dialog\"\n          aria-modal=\"true\"\n          aria-labelledby=\"phantom-confirm-title\"\n          aria-describedby=\"phantom-confirm-lead\"\n        >\n          <h2 id=\"phantom-confirm-title\">\n            Phantom users are now <span class=\"phantom-confirm-state\" id=\"phantom-confirm-state\">visible</span>\n          </h2>\n          <div class=\"phantom-confirm-body\" id=\"phantom-confirm-body\"></div>\n          <div class=\"phantom-confirm-actions\">\n            <label class=\"phantom-confirm-dismiss\">\n              <input type=\"checkbox\" id=\"phantom-confirm-dismiss\" />\n              Don't show again\n            </label>\n            <button class=\"phantom-confirm-action\" id=\"phantom-confirm-action\" type=\"button\">OK</button>\n          </div>\n        </section>\n      </div>\n    </div>\n    <script type=\"module\" src=\"/apps/_shell-js?blob=true\"></script>\n  <script>\n(() => {\n  document.addEventListener(\"click\", (event) => {\n    if (!(event.target instanceof Element)) return;\n    const anchor = event.target.closest(\"a[href]\");\n    if (!(anchor instanceof HTMLAnchorElement)) return;\n    if (anchor.target && anchor.target !== \"_self\") return;\n    let url;\n    try {\n      url = new URL(anchor.href, window.location.href);\n    } catch {\n      return;\n    }\n    if (url.origin !== window.location.origin) return;\n    if (!url.pathname.startsWith(\"/apps/\")) return;\n    if (window.top && window.top !== window) {\n      event.preventDefault();\n      window.top.location.href = url.href;\n      return;\n    }\n    anchor.target = \"_top\";\n  }, true);\n})();\n</script>\n</body>\n</html>\n","html":"<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, interactive-widget=resizes-content\" />\n    <title>Walkable</title>\n    <link\n      rel=\"icon\"\n      type=\"image/svg+xml\"\n      href=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' fill='none'%3E%3Ccircle cx='31' cy='31' r='4.6' fill='%233F5233' opacity='.32'/%3E%3Ccircle cx='23.5' cy='25' r='4.8' fill='%233F5233' opacity='.55'/%3E%3Ccircle cx='16.5' cy='17.5' r='5' fill='%234D6040' opacity='.8'/%3E%3Ccircle cx='11.5' cy='9.5' r='5.2' fill='%23C4983A'/%3E%3Ccircle cx='9.6' cy='7.2' r='1.5' fill='%23FFF8E7' opacity='.9'/%3E%3C/svg%3E\"\n    />\n    <style>\n:root {\n  --bg: #f5f2ec;\n  --nav-bg: #1f2a2e;\n  --nav-text: #f3efe8;\n  --accent: #e07a5f;\n  --muted: #a9a39b;\n  --shadow: 0 10px 30px rgba(0, 0, 0, 0.12);\n  --menu-shadow: 0 14px 40px rgba(0, 0, 0, 0.28);\n  --nav-height: 56px;\n  font-family: \"Avenir Next\", \"Avenir\", \"Futura\", \"Segoe UI\", sans-serif;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  margin: 0;\n  background: var(--bg);\n  color: #1f2a2e;\n}\n\n.shell {\n  min-height: 100vh;\n  /* Mobile browsers size 100vh to the viewport with their toolbars hidden;\n     100dvh is the height actually visible right now (see #app-frame). */\n  min-height: 100dvh;\n  display: flex;\n  flex-direction: column;\n}\n\n/* ---- The frame ---- */\n\n.frame {\n  height: var(--nav-height);\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 0 12px 0 8px;\n  background: var(--nav-bg);\n  color: var(--nav-text);\n  box-shadow: var(--shadow);\n}\n\n.zone {\n  display: flex;\n  align-items: center;\n  min-width: 0;\n}\n\n.zone-left {\n  gap: 4px;\n  flex: 0 0 auto;\n}\n\n.zone-center {\n  flex: 1 1 auto;\n  min-width: 0;\n  justify-content: flex-start;\n}\n\n.zone-right {\n  flex: 0 0 auto;\n  justify-content: flex-end;\n  gap: 4px;\n}\n\n.dev-frame {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  max-width: min(42vw, 420px);\n  padding: 4px 5px 4px 9px;\n  border: 1px solid color-mix(in srgb, #f1c40f 65%, transparent);\n  border-radius: 999px;\n  background: color-mix(in srgb, #f1c40f 18%, var(--nav-bg));\n  color: #fff4bd;\n  font-size: 0.72rem;\n  font-weight: 650;\n}\n\n.dev-frame[hidden] {\n  display: none;\n}\n\n.dev-frame-label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.dev-frame-disconnect {\n  border: 0;\n  border-radius: 999px;\n  padding: 3px 7px;\n  background: rgba(255, 255, 255, 0.14);\n  color: inherit;\n  font: inherit;\n  cursor: pointer;\n}\n\n.dev-frame-disconnect:hover {\n  background: rgba(255, 255, 255, 0.24);\n}\n\n@media (max-width: 720px) {\n  .dev-frame-label {\n    max-width: 22vw;\n  }\n}\n\n/* ---- Left: waffle + wordmark ---- */\n\n.apps {\n  position: relative;\n}\n\n.waffle {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 36px;\n  height: 36px;\n  border-radius: 8px;\n  color: var(--nav-text);\n  text-decoration: none;\n  font-size: 1.2rem;\n  line-height: 1;\n}\n\n.waffle:hover,\n.waffle[aria-expanded=\"true\"] {\n  background: rgba(255, 255, 255, 0.1);\n}\n\n.wordmark {\n  font-weight: 700;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  font-size: 0.82rem;\n  color: var(--nav-text);\n  text-decoration: none;\n  white-space: nowrap;\n}\n\n/* ---- Center: the location sentence ---- */\n\n.lens {\n  position: relative;\n  display: flex;\n  min-width: 0;\n}\n\n.location {\n  display: inline-flex;\n  align-items: baseline;\n  gap: 8px;\n  min-width: 0;\n}\n\n/* The location is a <button> so it can open the lens (\"open with…\") menu on\n * lens routes; on plain app routes it stays disabled and looks like the old\n * inert sentence. */\n.lens-chip {\n  background: none;\n  border: 0;\n  padding: 4px 8px;\n  margin: -4px -8px;\n  font: inherit;\n  color: inherit;\n  border-radius: 8px;\n  /* Flex item of .lens: natural width normally, shrinkable (the app-name\n   * ellipsizes) when the frame gets tight. NOT max-width:100% — that resolves\n   * circularly against the shrink-wrapped parent and clamps the name. */\n  min-width: 0;\n}\n\n.lens-chip:disabled {\n  cursor: default;\n}\n\n.lens-chip:not(:disabled) {\n  cursor: pointer;\n}\n\n.lens-chip:not(:disabled):hover,\n.lens-chip[aria-expanded=\"true\"] {\n  background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.lens-caret {\n  font-size: 0.7rem;\n  align-self: center;\n}\n\n.lens-caret[hidden] {\n  display: none;\n}\n\n.app-icon {\n  display: inline-flex;\n  align-items: center;\n  align-self: center;\n  flex: 0 0 auto;\n  font-size: 1.1rem;\n  line-height: 1;\n}\n\n.app-icon[hidden] {\n  display: none;\n}\n\n.app-icon img {\n  display: block;\n  height: 1.15rem;\n  width: 1.15rem;\n  object-fit: contain;\n  border-radius: 4px;\n}\n\n.app-name {\n  font-size: 1.1rem;\n  font-weight: 600;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.from {\n  font-size: 0.85rem;\n  color: var(--muted);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  flex: 0 1 auto;\n}\n\n.from[hidden] {\n  display: none;\n}\n\n/* ---- Right: account chip + menu ---- */\n\n.account {\n  position: relative;\n}\n\n.account-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  background: transparent;\n  border: 1px solid transparent;\n  border-radius: 999px;\n  padding: 4px 10px 4px 4px;\n  color: var(--nav-text);\n  font: inherit;\n  font-size: 0.9rem;\n  cursor: pointer;\n  max-width: 220px;\n}\n\n.account-chip:hover,\n.account-chip[aria-expanded=\"true\"] {\n  background: rgba(255, 255, 255, 0.1);\n  border-color: rgba(255, 255, 255, 0.14);\n}\n\n.account[data-auth-state=\"signed-out\"] .account-chip {\n  border-color: rgba(255, 255, 255, 0.32);\n  padding: 6px 12px;\n  font-weight: 700;\n}\n\n.account[data-auth-state=\"signed-out\"] .account-chip:hover,\n.account[data-auth-state=\"signed-out\"] .account-chip:focus-visible {\n  background: rgba(255, 255, 255, 0.14);\n  border-color: rgba(255, 255, 255, 0.5);\n}\n\n.account[data-auth-state=\"signed-out\"] .avatar,\n.account[data-auth-state=\"signed-out\"] .caret {\n  display: none;\n}\n\n/* Persistent frame toggle for phantom users: shared synthetic accounts whose\n * pages are included or excluded by the user's private cred rating on each\n * host-provided phantom root. */\n.phantom-chip[hidden] {\n  display: none;\n}\n\n.phantom-chip {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 7px;\n  min-width: 100px;\n  height: 34px;\n  flex: 0 0 auto;\n  background: rgba(226, 226, 226, 0.1);\n  color: rgba(243, 239, 232, 0.62);\n  border: 1px solid rgba(243, 239, 232, 0.22);\n  border-radius: 999px;\n  padding: 0 13px;\n  font: inherit;\n  font-size: 0.78rem;\n  font-weight: 800;\n  text-transform: uppercase;\n  cursor: pointer;\n  opacity: 0.78;\n  position: relative;\n  transition: background 120ms ease, border-color 120ms ease, color 120ms ease, opacity 120ms ease;\n}\n\n.phantom-chip[data-state=\"on\"] {\n  background: #b83f35;\n  color: #fff8f6;\n  border-color: rgba(255, 198, 185, 0.9);\n  opacity: 1;\n}\n\n.phantom-chip[data-busy=\"true\"] {\n  cursor: wait;\n  opacity: 0.68;\n}\n\n.phantom-chip[data-busy=\"true\"] .phantom-chip-short,\n.phantom-chip[data-busy=\"true\"] .phantom-chip-label {\n  visibility: hidden;\n}\n\n.phantom-chip::after {\n  content: \"\";\n  display: none;\n  position: absolute;\n  width: 14px;\n  height: 14px;\n  border: 2px solid currentColor;\n  border-right-color: transparent;\n  border-radius: 999px;\n}\n\n.phantom-chip[data-busy=\"true\"]::after {\n  display: block;\n  animation: phantom-spin 700ms linear infinite;\n}\n\n@keyframes phantom-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .phantom-chip[data-busy=\"true\"]::after {\n    animation: none;\n  }\n}\n\n.phantom-chip:hover,\n.phantom-chip:focus-visible {\n  background: rgba(226, 226, 226, 0.18);\n  border-color: rgba(243, 239, 232, 0.36);\n  color: rgba(243, 239, 232, 0.84);\n}\n\n.phantom-chip[data-state=\"on\"]:hover,\n.phantom-chip[data-state=\"on\"]:focus-visible {\n  background: #cb4b3f;\n  border-color: rgba(255, 220, 212, 0.95);\n  color: #fff;\n}\n\n.phantom-chip-short {\n  display: none;\n  letter-spacing: 0;\n}\n\n.phantom-chip-label {\n  white-space: nowrap;\n}\n\n/* The Phantoms chip's positioning context. */\n.phantom {\n  position: relative;\n  display: inline-flex;\n}\n\n.modal-backdrop {\n  position: fixed;\n  inset: 0;\n  z-index: 100;\n  display: grid;\n  place-items: center;\n  padding: 18px;\n  background: rgba(16, 21, 23, 0.54);\n}\n\n.modal-backdrop[hidden] {\n  display: none;\n}\n\n.phantom-confirm-modal {\n  width: min(420px, 100%);\n  border-radius: 12px;\n  background: #fff;\n  color: #1f2a2e;\n  box-shadow: var(--menu-shadow);\n  padding: 18px;\n  display: grid;\n  gap: 12px;\n}\n\n.phantom-confirm-modal h2 {\n  margin: 0;\n  font-size: 1.12rem;\n  line-height: 1.25;\n}\n\n.phantom-confirm-modal p {\n  margin: 0;\n  color: #5f676b;\n  line-height: 1.45;\n}\n\n.phantom-confirm-body {\n  display: grid;\n  gap: 10px;\n}\n\n/* The toggle word (\"visible\" / \"invisible\") in the modal title, strongly\n   highlighted so the new state reads at a glance. */\n.phantom-confirm-state {\n  font-weight: 800;\n  color: #173a2a;\n  background: #ffe39a;\n  padding: 0 0.28em;\n  border-radius: 4px;\n}\n\n/* The collapsed \"more…\" detail under the lead paragraph. */\n.phantom-confirm-more > summary {\n  cursor: pointer;\n  color: #2e7d5b;\n  font-weight: 600;\n  list-style: none;\n}\n\n.phantom-confirm-more > summary::-webkit-details-marker {\n  display: none;\n}\n\n.phantom-confirm-more[open] > summary {\n  margin-bottom: 8px;\n}\n\n/* `.phantom-confirm-modal p` zeroes margins and only the body's direct children\n   get the grid gap, so stacked paragraphs (the two inside the disclosure) would\n   otherwise run together. Restore the break between consecutive paragraphs. */\n.phantom-confirm-body p + p {\n  margin-top: 0.7em;\n}\n\n.phantom-confirm-actions {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  flex-wrap: wrap;\n}\n\n.phantom-confirm-dismiss {\n  display: inline-flex;\n  align-items: center;\n  gap: 7px;\n  font-size: 0.9rem;\n  color: #5f676b;\n  cursor: pointer;\n}\n\n.phantom-confirm-dismiss input {\n  width: 15px;\n  height: 15px;\n  accent-color: #2e7d5b;\n  cursor: pointer;\n}\n\n.phantom-confirm-action {\n  min-height: 36px;\n  border-radius: 8px;\n  padding: 0 13px;\n  font: inherit;\n  font-weight: 700;\n  cursor: pointer;\n  border: 1px solid #1f2a2e;\n  background: #1f2a2e;\n  color: #fff;\n}\n\n.frame-extension-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 7px;\n  position: relative;\n  min-width: 34px;\n  height: 34px;\n  flex: 0 0 auto;\n  border: 1px solid rgba(185, 213, 188, 0.42);\n  border-radius: 999px;\n  padding: 0 12px;\n  background: rgba(106, 143, 112, 0.22);\n  color: rgba(244, 250, 241, 0.9);\n  font: inherit;\n  font-size: 0.8rem;\n  font-weight: 800;\n  cursor: pointer;\n  transition: background 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;\n}\n\n.frame-extension-button[hidden] {\n  display: none;\n}\n\n.frame-extension-label {\n  white-space: nowrap;\n}\n\n.frame-extension-icon,\n.frame-extension-icon svg {\n  width: 18px;\n  height: 18px;\n}\n\n.frame-extension-icon {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  color: rgba(218, 240, 214, 0.95);\n}\n\n.frame-extension-icon svg {\n  display: block;\n}\n\n.frame-extension-icon svg {\n  fill: none;\n  stroke: currentColor;\n  stroke-width: 1.9;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n.frame-extension-badge {\n  position: absolute;\n  top: -4px;\n  right: -4px;\n  min-width: 18px;\n  height: 18px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border: 2px solid var(--nav-bg);\n  border-radius: 999px;\n  padding: 0 4px;\n  background: #8f3d2d;\n  color: #fff;\n  font-size: 0.66rem;\n  font-weight: 800;\n  line-height: 1;\n}\n\n.frame-extension-button:hover,\n.frame-extension-button:focus-visible {\n  background: rgba(126, 162, 130, 0.34);\n  border-color: rgba(214, 237, 209, 0.72);\n  color: #fff;\n}\n\n.frame-extension-button:focus-visible {\n  outline: 2px solid rgba(214, 237, 209, 0.55);\n  outline-offset: 2px;\n}\n\n.account-name {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.caret {\n  font-size: 0.7rem;\n  opacity: 0.7;\n}\n\n.avatar {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 28px;\n  height: 28px;\n  flex: 0 0 28px;\n  border-radius: 50%;\n  background: var(--accent);\n  color: #fff;\n  font-size: 0.85rem;\n  font-weight: 700;\n  text-transform: uppercase;\n  background-size: cover;\n  background-position: center;\n}\n\n.avatar.lg {\n  width: 40px;\n  height: 40px;\n  flex-basis: 40px;\n  font-size: 1.1rem;\n}\n\n/* ---- Frame dropdowns ---- */\n\n.apps-menu,\n.account-menu,\n.lens-menu {\n  position: absolute;\n  top: calc(100% + 8px);\n  min-width: 260px;\n  max-width: 320px;\n  background: #fff;\n  color: #1f2a2e;\n  border-radius: 12px;\n  box-shadow: var(--menu-shadow);\n  padding: 8px;\n  z-index: 50;\n}\n\n.apps-menu {\n  left: 0;\n  max-height: min(70vh, 520px);\n  overflow-y: auto;\n}\n\n.account-menu {\n  right: 0;\n}\n\n.lens-menu {\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n.apps-menu[hidden],\n.account-menu[hidden],\n.lens-menu[hidden] {\n  display: none;\n}\n\n.account-active {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 10px 12px;\n}\n\n.account-active-text {\n  min-width: 0;\n}\n\n.account-active-name {\n  font-weight: 600;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.account-active-sub {\n  font-size: 0.8rem;\n  color: #6b6b6b;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.menu-sep {\n  height: 1px;\n  background: #ececec;\n  margin: 6px 4px;\n}\n\n.menu-label {\n  font-size: 0.7rem;\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n  color: #9a9a9a;\n  padding: 4px 12px;\n}\n\n.menu-item {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  width: 100%;\n  padding: 9px 12px;\n  border: none;\n  border-radius: 8px;\n  background: transparent;\n  color: inherit;\n  font: inherit;\n  font-size: 0.9rem;\n  text-align: left;\n  text-decoration: none;\n  cursor: pointer;\n}\n\n.menu-item:hover {\n  background: #f3f0ea;\n}\n\n.menu-item[aria-current=\"page\"] {\n  background: #f3f0ea;\n  font-weight: 600;\n}\n\n.menu-item .avatar {\n  width: 26px;\n  height: 26px;\n  flex-basis: 26px;\n  font-size: 0.8rem;\n}\n\n.menu-item .menu-item-text {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.menu-item .menu-glyph {\n  display: inline-flex;\n  width: 26px;\n  justify-content: center;\n  opacity: 0.7;\n}\n\n.menu-item.danger {\n  color: #b3261e;\n}\n\n/* \"Coming soon\" rows: visible roadmap, dimmed and non-interactive. */\n.menu-item.is-soon {\n  cursor: default;\n  opacity: 0.5;\n}\n\n.menu-item.is-soon:hover {\n  background: transparent;\n}\n\n.menu-soon-tag {\n  margin-left: auto;\n  flex: 0 0 auto;\n  font-size: 0.62rem;\n  letter-spacing: 0.05em;\n  text-transform: uppercase;\n  color: #8a8a8a;\n  border: 1px solid #e0e0e0;\n  border-radius: 999px;\n  padding: 1px 7px;\n}\n\n/* A switchable account paired with its \"open in a new window\" action.\n   The switch button flex-grows; the icon stays a fixed icon button (#342). */\n.account-other-row {\n  display: flex;\n  align-items: center;\n}\n\n.account-other-row .account-other {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.account-new-window {\n  flex: 0 0 auto;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 30px;\n  height: 30px;\n  margin-right: 6px;\n  border: none;\n  border-radius: 8px;\n  background: transparent;\n  color: inherit;\n  font: inherit;\n  font-size: 0.95rem;\n  opacity: 0.55;\n  cursor: pointer;\n}\n\n.account-new-window:hover {\n  background: #f3f0ea;\n  opacity: 1;\n}\n\n/* Shared role accounts: \"act as a role\" rows + the acting-as band. A role wears\n   a distinct marker so a borrowed identity is never mistaken for one's own. */\n.avatar.role {\n  background: #2f6f6a;\n  color: #fff;\n}\n\n.account-role .menu-item-text {\n  font-style: italic;\n}\n\n/* When the account chip / menu is currently acting as a role, tint its avatar. */\n.account[data-acting-as-role=\"true\"] .avatar {\n  box-shadow: 0 0 0 2px #2f6f6a;\n}\n\n.account-acting-band {\n  padding: 8px 12px;\n  background: #eef5f4;\n  border-radius: 8px;\n  margin: 4px 8px;\n}\n\n.account-acting-note {\n  font-size: 0.8rem;\n  opacity: 0.8;\n  margin-bottom: 6px;\n}\n\n.account-switch-back {\n  color: #2f6f6a;\n  font-weight: 600;\n}\n\n:root[data-theme=\"dark\"] .account-acting-band {\n  background: #1c2b2a;\n}\n\n/* A teaching-band row: primary item + trailing help link. */\n.menu-row {\n  display: flex;\n  align-items: center;\n  gap: 2px;\n}\n\n.menu-row > .menu-item {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.menu-info {\n  flex: 0 0 auto;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 30px;\n  height: 30px;\n  border-radius: 8px;\n  color: #9a9a9a;\n  text-decoration: none;\n  font-size: 0.95rem;\n}\n\n.menu-info:hover {\n  background: #f3f0ea;\n  color: #1f2a2e;\n}\n\n.primary-button,\n.secondary-button {\n  min-width: 72px;\n  height: 36px;\n  border-radius: 8px;\n  font: inherit;\n  font-size: 0.9rem;\n  cursor: pointer;\n}\n\n.primary-button {\n  border: 1px solid #1f2a2e;\n  background: #1f2a2e;\n  color: #fff;\n}\n\n.secondary-button {\n  border: 1px solid #d7d2ca;\n  background: #fff;\n  color: #1f2a2e;\n}\n\n.primary-button:disabled,\n.secondary-button:disabled {\n  cursor: default;\n  opacity: 0.55;\n}\n\n/* ---- Content ---- */\n\n.content {\n  flex: 1;\n  min-height: 0;\n  position: relative;\n}\n\n/* ---- Public (chromeless) mode ----\n   Public read-only sites (docs/decisions/public-read-only-sites.md) hide the frame\n   for anonymous visitors and offer Login as a slim footer link. The server sets\n   <html data-chrome=\"public\"> in <head> so the frame never flashes pre-paint; the\n   shell sets data-signed-in once auth resolves, restoring full chrome for a\n   signed-in curator. Inert (footer stays display:none) outside public mode. */\n.public-footer {\n  display: none;\n}\nhtml[data-chrome=\"public\"] .frame {\n  display: none;\n}\nhtml[data-chrome=\"public\"] .public-footer {\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  padding: 10px 16px;\n  border-top: 1px solid rgba(31, 42, 46, 0.1);\n  background: var(--bg);\n}\nhtml[data-chrome=\"public\"][data-signed-in=\"true\"] .frame {\n  display: flex;\n}\nhtml[data-chrome=\"public\"][data-signed-in=\"true\"] .public-footer {\n  display: none;\n}\n.public-footer-link {\n  color: var(--muted);\n  font-size: 13px;\n  text-decoration: none;\n}\n.public-footer-link:hover {\n  color: #1f2a2e;\n  text-decoration: underline;\n}\n\n.app-error-banner {\n  position: absolute;\n  z-index: 30;\n  top: 12px;\n  left: 12px;\n  right: 12px;\n  max-width: 880px;\n  margin: 0 auto;\n  display: flex;\n  align-items: flex-start;\n  gap: 14px;\n  padding: 12px;\n  border: 1px solid #d78a5c;\n  border-radius: 10px;\n  background: #fff8f0;\n  color: #31251d;\n  box-shadow: 0 12px 30px rgba(31, 42, 46, 0.18);\n}\n\n/* Trust referral, not a prompt: a quiet note that a shared link asked for a\n * below-horizon lens, pointing at TrustLamp where an informed decision is\n * possible (decisions/lens-switcher-network-tier.md). Deliberately calm —\n * neutral surface, no warning colors: nothing is wrong, something is hidden. */\n.lens-referral-banner {\n  position: absolute;\n  z-index: 29;\n  top: 12px;\n  left: 12px;\n  right: 12px;\n  max-width: 880px;\n  margin: 0 auto;\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 14px;\n  border: 1px solid #d5d5d0;\n  border-radius: 10px;\n  background: #fbfbf8;\n  color: #31353a;\n  font-size: 0.9rem;\n  box-shadow: 0 8px 22px rgba(31, 42, 46, 0.12);\n}\n\n.lens-referral-banner[hidden] {\n  display: none;\n}\n\n.lens-referral-text {\n  min-width: 0;\n}\n\n.lens-referral-link {\n  white-space: nowrap;\n  color: inherit;\n  font-weight: 600;\n}\n\n.lens-referral-dismiss {\n  margin-left: auto;\n  background: none;\n  border: 0;\n  cursor: pointer;\n  color: inherit;\n  font-size: 0.9rem;\n  padding: 4px;\n}\n\n:root[data-theme=\"dark\"] .lens-referral-banner {\n  background: var(--surface);\n  color: var(--on-surface);\n  border-color: rgba(255, 255, 255, 0.16);\n}\n\n/* Mirror-on-demand fetch progress: shown while the host caches a publisher app\n   the user just opened (decision app-references-are-urls-invisible-cache §4). */\n.mirror-fetch-banner {\n  position: absolute;\n  z-index: 29;\n  top: 12px;\n  left: 12px;\n  right: 12px;\n  max-width: 880px;\n  margin: 0 auto;\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 14px;\n  border: 1px solid #d5d5d0;\n  border-radius: 10px;\n  background: #fbfbf8;\n  color: #31353a;\n  font-size: 0.9rem;\n  box-shadow: 0 8px 22px rgba(31, 42, 46, 0.12);\n}\n\n.mirror-fetch-banner[hidden] {\n  display: none;\n}\n\n.mirror-fetch-spinner {\n  flex: 0 0 auto;\n  width: 14px;\n  height: 14px;\n  border: 2px solid #d5d5d0;\n  border-top-color: #31353a;\n  border-radius: 50%;\n  animation: mirror-fetch-spin 0.9s linear infinite;\n}\n\n.mirror-fetch-banner.mirror-fetch-error .mirror-fetch-spinner {\n  display: none;\n}\n\n@keyframes mirror-fetch-spin {\n  to { transform: rotate(360deg); }\n}\n\n:root[data-theme=\"dark\"] .mirror-fetch-banner {\n  background: var(--surface);\n  color: var(--on-surface);\n  border-color: rgba(255, 255, 255, 0.16);\n}\n\n:root[data-theme=\"dark\"] .mirror-fetch-spinner {\n  border-color: rgba(255, 255, 255, 0.24);\n  border-top-color: var(--on-surface);\n}\n\n.app-error-banner[hidden] {\n  display: none;\n}\n\n.app-error-copy {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.app-error-title {\n  font-weight: 800;\n  font-size: 0.95rem;\n  line-height: 1.25;\n}\n\n.app-error-message {\n  margin-top: 3px;\n  color: #5f4937;\n  font-size: 0.88rem;\n  line-height: 1.35;\n}\n\n.app-error-details {\n  margin-top: 6px;\n  color: #71563e;\n  font-size: 0.78rem;\n}\n\n.app-error-details summary {\n  width: fit-content;\n  cursor: pointer;\n  font-weight: 700;\n}\n\n.app-error-details p {\n  margin: 5px 0 0;\n  overflow-wrap: anywhere;\n}\n\n.app-error-actions {\n  display: flex;\n  flex: 0 0 auto;\n  gap: 8px;\n  align-items: center;\n}\n\n.app-error-reload,\n.app-error-dismiss {\n  min-height: 34px;\n  border-radius: 8px;\n  padding: 0 11px;\n  font: inherit;\n  font-size: 0.82rem;\n  font-weight: 800;\n  cursor: pointer;\n  white-space: nowrap;\n}\n\n.app-error-reload {\n  border: 1px solid #1f2a2e;\n  background: #1f2a2e;\n  color: #fff;\n}\n\n.app-error-dismiss {\n  border: 1px solid #d9b98e;\n  background: #fff;\n  color: #5f4937;\n}\n\n#app-frame {\n  width: 100%;\n  height: calc(100vh - var(--nav-height));\n  /* 100vh is the *largest* viewport on mobile (toolbars retracted), so on a\n     phone the bottom of the frame sat under the browser's own toolbars, and\n     apps pinned to the bottom (e.g. the Chat composer) were unreachable:\n     touches inside the iframe never scroll the shell to reveal it. The\n     dynamic viewport tracks the toolbars (and, with interactive-widget=\n     resizes-content in index.html, the on-screen keyboard on Android; iOS\n     Safari ignores that and pans to the focused field instead). */\n  height: calc(100dvh - var(--nav-height));\n  border: none;\n  display: block;\n  background: #fff;\n}\n\n/* ---- Mobile: stay a single row; shed the least-needed bits first ---- */\n\n@media (max-width: 600px) {\n  .wordmark {\n    display: none;\n  }\n  .account-name,\n  .caret {\n    display: none;\n  }\n  .account-chip {\n    padding: 4px;\n  }\n  .phantom-chip {\n    min-width: 34px;\n    width: 34px;\n    padding: 0;\n  }\n  .phantom-chip-short {\n    display: inline;\n  }\n  .phantom-chip-label {\n    display: none;\n  }\n  .frame-extension-label {\n    display: none;\n  }\n  .frame-extension-button {\n    width: 34px;\n    padding: 0;\n  }\n  .app-name {\n    font-size: 1rem;\n  }\n  .app-error-banner {\n    top: 8px;\n    left: 8px;\n    right: 8px;\n    flex-direction: column;\n    gap: 10px;\n  }\n  .app-error-actions {\n    width: 100%;\n  }\n  .app-error-reload,\n  .app-error-dismiss {\n    flex: 1 1 0;\n  }\n}\n\n/* ---- Dark theme ----\n   Applied when the shell sets <html data-theme=\"dark\"> from the reader's account\n   preference (app.ts applyTheme; pre-painted by the inline <head> script). The nav\n   bar is already dark in both themes; these rules darken the remaining light\n   surfaces — the popover menus, the Phantoms modal, buttons, the app-load backdrop,\n   and the data-error banner — so the whole shell, including the modal, follows the\n   setting. Light mode is the default and left untouched. */\n:root[data-theme=\"dark\"] {\n  --bg: #15191b;\n  --muted: #8b9398;\n  --shadow: 0 10px 30px rgba(0, 0, 0, 0.45);\n  --menu-shadow: 0 14px 40px rgba(0, 0, 0, 0.6);\n  --surface: #1f262a;\n  --surface-2: #283034;\n  --on-surface: #e9eef0;\n  --on-surface-muted: #9aa3a8;\n  --border-soft: #323b40;\n  --accent-green: #2e7d5b;\n  --accent-green-border: #3c8f6a;\n}\n\n:root[data-theme=\"dark\"] body {\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .apps-menu,\n:root[data-theme=\"dark\"] .account-menu,\n:root[data-theme=\"dark\"] .lens-menu {\n  background: var(--surface);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .account-active-sub,\n:root[data-theme=\"dark\"] .menu-label,\n:root[data-theme=\"dark\"] .menu-info {\n  color: var(--on-surface-muted);\n}\n\n:root[data-theme=\"dark\"] .menu-sep {\n  background: var(--border-soft);\n}\n\n:root[data-theme=\"dark\"] .menu-item:hover,\n:root[data-theme=\"dark\"] .menu-item[aria-current=\"page\"],\n:root[data-theme=\"dark\"] .account-new-window:hover {\n  background: var(--surface-2);\n}\n\n:root[data-theme=\"dark\"] .menu-info:hover {\n  background: var(--surface-2);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .menu-item.danger {\n  color: #f08a82;\n}\n\n:root[data-theme=\"dark\"] .menu-soon-tag {\n  color: var(--on-surface-muted);\n  border-color: var(--border-soft);\n}\n\n:root[data-theme=\"dark\"] .secondary-button {\n  background: var(--surface-2);\n  border-color: var(--border-soft);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .primary-button {\n  border-color: var(--accent-green-border);\n  background: var(--accent-green);\n  color: #fff;\n}\n\n/* The Phantoms modal follows the theme. */\n:root[data-theme=\"dark\"] .phantom-confirm-modal {\n  background: var(--surface);\n  color: var(--on-surface);\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-modal p {\n  color: var(--on-surface-muted);\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-more > summary {\n  color: #6fc69b;\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-dismiss {\n  color: var(--on-surface-muted);\n}\n\n:root[data-theme=\"dark\"] .phantom-confirm-action {\n  border-color: var(--accent-green-border);\n  background: var(--accent-green);\n  color: #fff;\n}\n\n/* The toggle-state highlight stays a warm chip; darken its text for contrast. */\n:root[data-theme=\"dark\"] .phantom-confirm-state {\n  color: #1a2e22;\n}\n\n:root[data-theme=\"dark\"] #app-frame {\n  background: var(--bg);\n}\n\n/* Data-error banner: warm-dark amber instead of cream. */\n:root[data-theme=\"dark\"] .app-error-banner {\n  background: #2a211a;\n  border-color: #8a5a36;\n  color: #f0e3d6;\n}\n\n:root[data-theme=\"dark\"] .app-error-message {\n  color: #d8c2ab;\n}\n\n:root[data-theme=\"dark\"] .app-error-details {\n  color: #c9b196;\n}\n\n:root[data-theme=\"dark\"] .app-error-reload {\n  border-color: var(--accent-green-border);\n  background: var(--accent-green);\n}\n\n:root[data-theme=\"dark\"] .app-error-dismiss {\n  background: var(--surface-2);\n  border-color: #8a5a36;\n  color: #f0e3d6;\n}\n\n:root[data-theme=\"dark\"] .public-footer-link:hover {\n  color: var(--on-surface);\n}\n\n</style><script>\n      // Pre-paint theme: apply the cached resolved scheme (or the OS preference) so a\n      // returning reader's dark mode never flashes light before app.js loads the\n      // authoritative account preference and reconciles. Mirror of THEME_PAINT_HINT_KEY\n      // / applyTheme in app.ts.\n      (function () {\n        try {\n          var hint = localStorage.getItem(\"wkbl.themeResolved\");\n          var resolved =\n            hint === \"light\" || hint === \"dark\"\n              ? hint\n              : window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n                ? \"dark\"\n                : \"light\";\n          document.documentElement.dataset.theme = resolved;\n        } catch (e) {}\n      })();\n    </script>\n  </head>\n  <body>\n    <div class=\"shell\">\n      <header class=\"frame\">\n        <div class=\"zone zone-left\">\n          <div class=\"apps\" id=\"apps\">\n            <a\n              class=\"waffle\"\n              id=\"waffle\"\n              href=\"/\"\n              title=\"Apps\"\n              aria-label=\"Apps\"\n              aria-haspopup=\"menu\"\n              aria-expanded=\"false\"\n              aria-controls=\"apps-menu\"\n            >\n              <span aria-hidden=\"true\">☰</span>\n            </a>\n            <div class=\"apps-menu\" id=\"apps-menu\" role=\"menu\" hidden></div>\n          </div>\n          <a class=\"wordmark\" id=\"home-link\" href=\"/\">Walkable</a>\n        </div>\n\n        <div class=\"zone zone-center\">\n          <div class=\"lens\" id=\"lens\">\n            <button\n              class=\"location lens-chip\"\n              id=\"lens-chip\"\n              type=\"button\"\n              disabled\n              aria-haspopup=\"menu\"\n              aria-expanded=\"false\"\n              aria-controls=\"lens-menu\"\n            >\n              <span class=\"app-icon\" id=\"app-icon\" aria-hidden=\"true\" hidden></span>\n              <span class=\"app-name\" id=\"app-name\">App</span>\n              <span class=\"from\" id=\"from-owner\" hidden></span>\n              <span class=\"caret lens-caret\" id=\"lens-caret\" aria-hidden=\"true\" hidden>▾</span>\n            </button>\n            <div class=\"lens-menu\" id=\"lens-menu\" role=\"menu\" hidden></div>\n          </div>\n        </div>\n\n        <div class=\"zone zone-right\">\n          <div class=\"dev-frame\" id=\"dev-frame\" hidden>\n            <span class=\"dev-frame-label\" id=\"dev-frame-label\"></span>\n            <button class=\"dev-frame-disconnect\" id=\"dev-frame-disconnect\" type=\"button\">Disconnect</button>\n          </div>\n          <div class=\"phantom\" id=\"phantom\">\n            <button\n              class=\"phantom-chip\"\n              id=\"phantom-chip\"\n              type=\"button\"\n              hidden\n              title=\"Phantom users are hidden\"\n              aria-pressed=\"false\"\n              data-state=\"off\"\n            >\n              <span class=\"phantom-chip-short\" aria-hidden=\"true\">Ph</span>\n              <span class=\"phantom-chip-label\">Phantoms</span>\n            </button>\n          </div>\n          <div class=\"account\" id=\"account\" data-auth-state=\"signed-out\">\n            <button\n              class=\"account-chip\"\n              id=\"account-chip\"\n              aria-haspopup=\"false\"\n              aria-expanded=\"false\"\n              aria-label=\"Log in or sign up\"\n            >\n              <span class=\"avatar\" id=\"account-avatar\" aria-hidden=\"true\">·</span>\n              <span class=\"account-name\" id=\"user-label\">Log in / Sign up</span>\n              <span class=\"caret\" aria-hidden=\"true\">▾</span>\n            </button>\n            <div class=\"account-menu\" id=\"account-menu\" role=\"menu\" hidden></div>\n          </div>\n        </div>\n      </header>\n\n      <main class=\"content\">\n        <div class=\"lens-referral-banner\" id=\"lens-referral-banner\" role=\"status\" aria-live=\"polite\" hidden>\n          <span class=\"lens-referral-text\" id=\"lens-referral-text\"></span>\n          <a class=\"lens-referral-link\" id=\"lens-referral-link\" href=\"/apps/app-trustlamp\">See who trusts them</a>\n          <button class=\"lens-referral-dismiss\" id=\"lens-referral-dismiss\" type=\"button\" aria-label=\"Dismiss\">✕</button>\n        </div>\n        <div class=\"mirror-fetch-banner\" id=\"mirror-fetch-banner\" role=\"status\" aria-live=\"polite\" hidden>\n          <span class=\"mirror-fetch-spinner\" id=\"mirror-fetch-spinner\" aria-hidden=\"true\"></span>\n          <span class=\"mirror-fetch-text\" id=\"mirror-fetch-text\"></span>\n        </div>\n        <div class=\"app-error-banner\" id=\"app-error-banner\" role=\"status\" aria-live=\"polite\" hidden>\n          <div class=\"app-error-copy\">\n            <div class=\"app-error-title\" id=\"app-error-title\">This app could not load data</div>\n            <div class=\"app-error-message\" id=\"app-error-message\"></div>\n            <details class=\"app-error-details\">\n              <summary>Details</summary>\n              <p id=\"app-error-detail\"></p>\n            </details>\n          </div>\n          <div class=\"app-error-actions\">\n            <button class=\"app-error-reload\" id=\"app-error-reload\" type=\"button\">Reload app</button>\n            <button class=\"app-error-dismiss\" id=\"app-error-dismiss\" type=\"button\" aria-label=\"Dismiss app data warning\">Dismiss</button>\n          </div>\n        </div>\n        <iframe\n          id=\"app-frame\"\n          referrerpolicy=\"no-referrer\"\n          sandbox=\"allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation\"\n          title=\"App\"\n        ></iframe>\n      </main>\n\n      <!-- Public read-only sites: the only chrome an anonymous visitor sees. Hidden\n           by default; shown by CSS only under <html data-chrome=\"public\"> while\n           signed out (see docs/decisions/public-read-only-sites.md). -->\n      <footer class=\"public-footer\" id=\"public-footer\">\n        <a class=\"public-footer-link\" id=\"public-login-link\" href=\"/login\">Log in</a>\n      </footer>\n\n      <div class=\"modal-backdrop\" id=\"phantom-confirm-backdrop\" hidden>\n        <section\n          class=\"phantom-confirm-modal\"\n          role=\"dialog\"\n          aria-modal=\"true\"\n          aria-labelledby=\"phantom-confirm-title\"\n          aria-describedby=\"phantom-confirm-lead\"\n        >\n          <h2 id=\"phantom-confirm-title\">\n            Phantom users are now <span class=\"phantom-confirm-state\" id=\"phantom-confirm-state\">visible</span>\n          </h2>\n          <div class=\"phantom-confirm-body\" id=\"phantom-confirm-body\"></div>\n          <div class=\"phantom-confirm-actions\">\n            <label class=\"phantom-confirm-dismiss\">\n              <input type=\"checkbox\" id=\"phantom-confirm-dismiss\" />\n              Don't show again\n            </label>\n            <button class=\"phantom-confirm-action\" id=\"phantom-confirm-action\" type=\"button\">OK</button>\n          </div>\n        </section>\n      </div>\n    </div>\n    <script type=\"module\" src=\"/apps/_shell-js?blob=true\"></script>\n  <script>\n(() => {\n  document.addEventListener(\"click\", (event) => {\n    if (!(event.target instanceof Element)) return;\n    const anchor = event.target.closest(\"a[href]\");\n    if (!(anchor instanceof HTMLAnchorElement)) return;\n    if (anchor.target && anchor.target !== \"_self\") return;\n    let url;\n    try {\n      url = new URL(anchor.href, window.location.href);\n    } catch {\n      return;\n    }\n    if (url.origin !== window.location.origin) return;\n    if (!url.pathname.startsWith(\"/apps/\")) return;\n    if (window.top && window.top !== window) {\n      event.preventDefault();\n      window.top.location.href = url.href;\n      return;\n    }\n    anchor.target = \"_top\";\n  }, true);\n})();\n</script>\n</body>\n</html>\n"}