/* Shared styles for the builder.
 *
 * Visual system: "Estilo Material AI" (docs/design.md) — Google's brand
 * palette (blue #4285F4, red/yellow/green), Roboto, an 8/16/24px radius
 * scale, subtle Material elevation, restrained 200ms motion. This replaces
 * the earlier screen-for-screen FlutterFlow clone (Manrope, #2C70C5, pill
 * buttons) that this app wore during the migration off FlutterFlow — pixel
 * parity was a migration aid, not a permanent commitment (see
 * docs/ARCHITECTURE.md). The 72px app bar inside a 1440px container is kept
 * as-is: it is this app's own layout, not a FlutterFlow artifact, and changing
 * it is out of scope for a visual reskin. Every screen's body fills that same
 * container — the sidebar screens always did, and the Workspace/Admin used to
 * sit in a narrower 1000px column centred inside it, which read as a different
 * page format next to them (issue #10); home/index.html shares the same
 * container and gutter for the same reason.
 * Dark mode keeps its `data-theme="dark"` toggle, remembered per browser,
 * now mapped to Google's own dark-surface convention instead of the
 * FlutterFlow dark palette.
 *
 * One linked stylesheet rather than seven inline copies; page-specific rules
 * stay in the page that owns them.
 */

:root {
  --primary: #4285F4;
  --primary-dark: #3367D6;
  --secondary: #34A853;
  --accent: #00BCD4;
  --text: #3C4043;
  --text-2: rgba(60, 64, 67, .5);        /* section headings, hints */
  --text-3: rgba(60, 64, 67, .65);
  --bg: #ffffff;
  --bg-2: #ffffff;                       /* cards, fields */
  --bg-3: #F8F9FA;
  --line: rgba(60, 64, 67, .15);         /* field and button borders */
  --error: #EA4335;
  --success: #34A853;
  --warning: #FBBC05;
  --on-primary: #ffffff;
  --shadow-1: 0 1px 2px rgba(60, 64, 67, .1), 0 1px 3px rgba(60, 64, 67, .08);
  --shadow-2: 0 2px 4px rgba(0, 0, 0, .1);
  --shadow-lift: 0 8px 24px rgba(66, 133, 244, .18);
  --radius-sm: 8px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --radius: var(--radius-md);
  --container: 1440px;
  --gutter: 32px;
  --ease: ease-out;
  color-scheme: light;
}
:root[data-theme="dark"] {
  --text: #E8EAED;
  --text-2: rgba(232, 234, 237, .55);
  --text-3: rgba(232, 234, 237, .7);
  --bg: #202124;
  --bg-2: #292A2D;
  --bg-3: #35363A;
  --line: rgba(232, 234, 237, .14);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .35), 0 1px 3px rgba(0, 0, 0, .3);
  --shadow-2: 0 2px 4px rgba(0, 0, 0, .35);
  --shadow-lift: 0 8px 24px rgba(66, 133, 244, .28);
  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body { height: 100%; }
body {
  margin: 0; min-height: 100%; display: flex; flex-direction: column;
  font-family: "Roboto", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  font-size: 14px; color: var(--text); background: var(--bg); line-height: 1.5;
}
body > .shell { flex: 1 0 auto; }

a { color: inherit; }
h1, h2, h3 { line-height: 1.2; font-weight: 700; margin: 0; }
p { margin: 0 0 12px; }
code { font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; }
button { font-family: inherit; }

@keyframes fade-in-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: no-preference) {
  .content > .card:first-child, .auth-col { animation: fade-in-up .42s var(--ease) both; }
  /* auth.js adds this once the form has already animated in once, so the
     column doesn't replay the entrance on every later re-render (an eye-icon
     click, a tab switch, a failed login). */
  .auth-col.no-anim { animation: none; }
}

/* ── App bar ────────────────────────────────────────────────────────────── */

.topbar { background: var(--bg); }
.topbar-in {
  max-width: var(--container); margin: 0 auto; padding: 24px var(--gutter) 16px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 112px;
}
.logo { display: block; width: 250px; flex: none; }
.logo img { width: 100%; height: auto; display: block; }
/* The only logo asset with dark lettering; in dark mode the black "osters"
   would vanish, so invert lightness and keep the hues (the icon and the blue
   "App" survive the round trip). The footer uses the white variant as is. */
:root[data-theme="dark"] .topbar .logo img, :root[data-theme="dark"] .auth .logo img, :root[data-theme="dark"] .drawer .logo img { filter: invert(1) hue-rotate(180deg); }
.topnav { display: flex; align-items: center; gap: 32px; }
.topnav .who { font-size: 14px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The account menu: the email is the button, everything that belongs to the
   person (Account, Log Out) hangs under it instead of crowding the app bar. */
.account-menu { position: relative; }
.account-menu .who { background: none; border: 0; font: inherit; color: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; padding: 0; }
.account-menu .who::after { content: ''; width: 6px; height: 6px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translate(-2px, -2px); opacity: .6; }
.account-menu .who:hover { opacity: .75; }
.account-dropdown { position: absolute; right: 0; top: calc(100% + 10px); min-width: 180px; background: var(--bg-1); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow-2, 0 8px 24px rgba(0,0,0,.14)); padding: 6px; z-index: 60; display: flex; flex-direction: column; }
.account-dropdown a, .account-dropdown button { text-align: left; padding: 9px 12px; border-radius: 8px; font: inherit; font-size: 14px; text-decoration: none; color: var(--text-1); background: none; border: 0; cursor: pointer; }
.account-dropdown a:hover, .account-dropdown button:hover { background: var(--bg-3); }
/* Only the colour sets Log Out apart from Account (issue #79). The app bar used
   to carry Log Out itself, and its `.topnav .logout` rule (no padding, 16px)
   outranked the item rule above and set it crooked under Account - so there is
   no `.topnav .logout` any more, and `font` comes before `font-size` above:
   the shorthand after it quietly reset the size to whatever the page inherits. */
.account-dropdown .logout { color: var(--error); }
.account-dropdown[hidden] { display: none; }
.btn.nav { width: 160px; height: 48px; padding: 0; font-size: 16px; font-weight: 500; }
.icon-btn {
  width: 40px; height: 40px; border-radius: 50%; border: 0; background: var(--primary); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer; flex: none; padding: 0;
  transition: box-shadow .2s var(--ease), transform .1s var(--ease);
}
.icon-btn:hover { box-shadow: var(--shadow-lift); transform: translateY(-1px); }
.icon-btn.plain { background: transparent; color: var(--text); }
.icon-btn.plain:hover { box-shadow: none; background: var(--bg-3); }
.icon-btn svg { width: 22px; height: 22px; }
.menu-btn { display: none; }

/* Drawer (phone) */
.drawer-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, .5); z-index: 80; }
.drawer {
  position: fixed; top: 0; left: 0; bottom: 0; width: 304px; background: var(--bg); z-index: 81;
  padding: 40px 16px; display: flex; flex-direction: column; gap: 24px; box-shadow: var(--shadow-lift);
}
.drawer .logo { width: 200px; }
.drawer a, .drawer button { font-size: 16px; text-decoration: none; text-align: left; background: none; border: 0; padding: 0; cursor: pointer; color: var(--text); font: inherit; font-size: 16px; }
.drawer .logout { color: var(--error); }

/* ── Footer ─────────────────────────────────────────────────────────────── */

.footer { background: var(--primary); color: #fff; margin-top: 96px; }
.footer-in {
  max-width: var(--container); margin: 0 auto; padding: 24px var(--gutter);
  display: flex; align-items: center; gap: 12px 32px; min-height: 90px;
  /* Seven items since the affiliate link (issue #161): on a ~1280px laptop
     the row no longer fits on one line, and without wrapping flex squeezes
     the copyright and the links into word-by-word columns. */
  flex-wrap: wrap;
}
.footer .logo { width: 140px; }
.footer .copy { font-size: 18px; }
.footer .spacer { flex: 1; }
.footer .mail { font-size: 18px; text-decoration: none; }
.footer .mail:hover { text-decoration: underline; }
.footer .gh { display: inline-flex; color: #fff; opacity: .9; transition: opacity .2s var(--ease); }
.footer .gh:hover { opacity: 1; }
.footer .gh svg { width: 40px; height: 40px; }

/* ── Page body ──────────────────────────────────────────────────────────── */

.shell { max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter); width: 100%; }
/* The body of a screen without a sidebar (Workspace, Admin). No max-width of
   its own: it spans the .shell exactly like `main` does next to the sidebar,
   so the left edge of the content lines up with the logo on every screen. */
.content { padding-top: 96px; }
.shell.with-side { display: grid; grid-template-columns: 220px 1fr; gap: 24px; align-items: start; padding-top: 32px; }

.page-title { font-size: 40px; font-weight: 700; line-height: 1.15; margin: 0 0 32px; letter-spacing: -.01em; }
.section-title { font-size: 26px; color: var(--text); font-weight: 700; margin: 0 0 28px; }
.section-title.sm { font-size: 20px; }
/* The Landing/About/FAQ/Sign In page header (page.js): h1 + status line, not
   inside a .card, so it needs its own rule rather than .card-head's tighter
   12px (that one separates a card's own h2/h3 from the card's body). */
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 28px; }
.page-head h1 { margin: 0; }
.lead { font-size: 16px; margin: 0 0 22px; color: var(--text-3); }
.muted { color: var(--text-3); }
.dim { color: var(--text-2); }
.small { font-size: 12px; }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.spacer { flex: 1; }

/* Sidebar: a plain list of page names, dimmed until that page exists. */
.sidebar { padding: 8px 0; position: sticky; top: 24px; }
.side-group { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-2); font-weight: 700; padding: 16px 0 6px; }
.side-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; margin: 0 -10px; border-radius: var(--radius-sm); text-decoration: none; font-size: 16px; font-weight: 500; color: var(--text); transition: background .15s var(--ease), color .15s var(--ease); }
.side-item:hover { background: var(--bg-3); }
.side-item.dim { opacity: .5; }
.side-item.active { color: var(--primary); background: rgba(66, 133, 244, .1); }
.side-item-stack { flex-direction: column; align-items: flex-start; gap: 2px; }
.side-item-row { display: flex; align-items: center; gap: 6px; }
.side-sub { font-size: 11px; font-weight: 400; color: var(--text-2); }
.side-badge { display: inline-flex; color: var(--text-2); }
.side-badge svg { width: 14px; height: 14px; }
.side-gap { margin-top: 14px; }
.dot { display: none; }
.crumb { font-size: 14px; color: var(--text-3); margin-bottom: 10px; display: flex; gap: 6px; }
.crumb a { text-decoration: none; }
.crumb a:hover { color: var(--primary); }

/* ── Surfaces ───────────────────────────────────────────────────────────── */

.card { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-md); box-shadow: var(--shadow-1); padding: 20px 24px; margin-bottom: 16px; }
.card h2 { font-size: 20px; margin-bottom: 12px; font-weight: 700; }
.card h3 { font-size: 16px; margin-bottom: 8px; font-weight: 500; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.card-head h2, .card-head h3 { margin: 0; }

/* ── Controls ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 48px; padding: 0 24px; border-radius: var(--radius-sm); font-size: 15px; font-weight: 600;
  cursor: pointer; text-decoration: none; border: 1.5px solid var(--line);
  background: var(--bg-2); color: var(--text);
  transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease), transform .1s var(--ease);
}
.btn:hover:not(:disabled) { background: var(--bg-3); transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0); }
.btn.primary { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
.btn.primary:hover:not(:disabled) { background: var(--primary-dark); border-color: var(--primary-dark); box-shadow: var(--shadow-lift); }
.btn.danger { border-color: var(--error); color: var(--error); }
.btn.danger:hover:not(:disabled) { background: rgba(234, 67, 53, .08); }
.btn.ghost { border-color: transparent; background: transparent; color: var(--text-3); }
.btn.ghost:hover:not(:disabled) { background: var(--bg-3); color: var(--text); transform: none; }
.btn.small { height: 36px; padding: 0 16px; font-size: 13px; }
.btn.wide { width: 230px; padding: 0; }
.btn.pill { height: 52px; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

label { display: block; font-weight: 500; font-size: 13px; margin-bottom: 6px; color: var(--text-3); }
.field { margin-bottom: 16px; }
.hint { font-size: 13px; color: var(--text-2); margin-top: 8px; }

/* A radio-card chooser (App hosting: Dockerfile vs bare container). */
.choice { display: grid; gap: 10px; margin-bottom: 16px; }
.choice label { display: flex; gap: 12px; align-items: flex-start; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); cursor: pointer; }
.choice label:has(input:checked) { border-color: var(--primary); background: rgba(66, 133, 244, .07); }
.choice label:has(input:disabled) { opacity: .55; cursor: not-allowed; }
.choice input[type="radio"] { margin-top: 3px; flex: none; }
.choice .title { display: block; font-weight: 600; }
.choice .desc { display: block; font-size: 13px; color: var(--text-2); margin-top: 2px; }

input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, select {
  width: 100%; height: 48px; padding: 0 14px; font: inherit; font-size: 15px; color: var(--text);
  border: 1.5px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-2);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
textarea { height: auto; min-height: 90px; padding: 10px 14px; resize: vertical; }
input::placeholder, textarea::placeholder { color: var(--text-2); }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(66, 133, 244, .18); }

/* Label-above field (docs/design.md: "no floating labels"). The eye button is
   positioned against .ff-input-wrap (input-only), not the whole field, so it
   centers on the input regardless of the label's height above it. */
.ff-field { display: flex; flex-direction: column-reverse; gap: 6px; margin-bottom: 20px; }
.ff-field label { position: static; margin: 0; font-size: 13px; font-weight: 500; color: var(--text-3); pointer-events: none; background: none; }
.ff-field:focus-within label { color: var(--primary); }
.ff-input-wrap { position: relative; }
.ff-input-wrap input { height: 48px; padding: 0 44px 0 14px; }
.ff-field .eye {
  position: absolute; right: 14px; top: 50%; transform: translateY(-50%); width: 24px; height: 24px; border: 0; background: none;
  color: var(--text-3); cursor: pointer; padding: 0; display: inline-flex;
}
.ff-field .eye svg { width: 22px; height: 22px; }

.input-suffix { display: flex; align-items: stretch; }
.input-suffix input { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.input-suffix .suffix {
  display: flex; align-items: center; padding: 0 14px; white-space: nowrap;
  border: 1.5px solid var(--line); border-left: 0; border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--bg-3); color: var(--text-3); font-size: 14px;
}

.check { display: flex; align-items: flex-start; gap: 10px; font-weight: 500; margin-bottom: 10px; }
.check input { margin-top: 4px; flex: none; width: auto; accent-color: var(--primary); }
.check.muted span { color: var(--text-3); }

/* ── Status ─────────────────────────────────────────────────────────────── */

.badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; background: var(--bg-3); color: var(--text-3); }
.badge.live { background: rgba(52, 168, 83, .15); color: var(--success); }
.badge.draft { background: rgba(251, 188, 5, .2); color: #8A6D00; }
.badge.error { background: rgba(234, 67, 53, .12); color: var(--error); }
.badge.agent { background: rgba(66, 133, 244, .12); color: var(--primary); }
/* "Paid feature" (billing.js): on the Custom domain card head and the SaaS
   card's Embed App row, and as a bare glyph next to Embed App in the sidebar,
   only while the account has not paid. Since issue #51 the mark is the way to
   pay - a <button> (Checkout) or an <a> (Payment Link) - so the browser's
   button chrome comes off and a hover says it is one. Not `font: inherit`:
   the shorthand would undo .badge's size and weight. */
.badge.paid { background: rgba(66, 133, 244, .12); color: var(--primary); border: 0; text-decoration: none; line-height: inherit; transition: background .15s var(--ease); }
.badge.paid svg { width: 14px; height: 14px; }
.side-badge.paid { color: var(--primary); border: 0; background: none; padding: 0; transition: color .15s var(--ease); }
button.badge.paid, a.badge.paid, button.side-badge.paid, a.side-badge.paid { cursor: pointer; }
button.badge.paid:hover:not(:disabled), a.badge.paid:hover { background: rgba(66, 133, 244, .24); }
button.side-badge.paid:hover:not(:disabled), a.side-badge.paid:hover { color: var(--primary-dark); }
.badge.paid:disabled, .side-badge.paid:disabled { opacity: .6; cursor: progress; }
.ok { color: var(--success); }
.bad { color: var(--error); }
.err-text { color: var(--error); font-size: 15px; margin: 16px 0; }

.loading { display: flex; align-items: center; gap: 24px; font-size: 16px; padding: 24px 0; }
.spin { width: 16px; height: 16px; border-radius: 50%; flex: none; border: 2px solid var(--line); border-top-color: var(--primary); animation: spin .7s linear infinite; }
.spin.big { width: 52px; height: 52px; border-width: 3px; border-color: rgba(66, 133, 244, .18); border-top-color: var(--primary); }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spin { animation-duration: 2s; } }

.empty { text-align: center; padding: 40px 20px; color: var(--text-3); }
.empty h3 { color: var(--text); }

.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 20px);
  background: #323232; color: #fff; padding: 14px 24px; border-radius: var(--radius-sm);
  font-size: 14px; box-shadow: var(--shadow-lift); opacity: 0; pointer-events: none;
  transition: opacity .2s var(--ease), transform .2s var(--ease); z-index: 60; max-width: 90vw;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.toast-error { background: var(--error); }

/* ── Tables ─────────────────────────────────────────────────────────────── */

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); }
/* A cell holding a URL or name with no natural break point (admin.js's Project
   columns) must not size the whole table by its longest unbroken token - that
   pushes the later columns past table-wrap's edge, hidden by its own scrollbar. */
.break-word { overflow-wrap: anywhere; }
th { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-3); font-weight: 700; }
tbody tr:last-child td { border-bottom: 0; }
/* A list inside a cell (admin.js's comparison suggestions, issue #203): the
   numbers are the product's and the criterion's place in the suggestion, so
   they stay, but in the cell's own gutter rather than the browser's 40px. */
td ol.cell-list { margin: 0; padding-left: 18px; }
td ol.cell-list li + li { margin-top: 4px; }
/* Text a person typed with its line breaks (the suggestion's note). */
.pre-line { white-space: pre-line; }

/* ── Modal ──────────────────────────────────────────────────────────────── */

.modal-backdrop { position: fixed; inset: 0; background: rgba(32, 33, 36, .5); display: flex; align-items: center; justify-content: center; padding: 20px; z-index: 70; }
.modal { background: var(--bg-2); border-radius: var(--radius-lg); box-shadow: var(--shadow-lift); padding: 24px; max-width: 440px; width: 100%; }
.modal h3 { font-size: 18px; margin-bottom: 8px; font-weight: 700; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; flex-wrap: wrap; }
/* The Embed App paywall (deploy.js): three actions and a feature list, so it
   needs more room than a yes/no confirm, and the list is prose, not a menu. */
.modal.paywall { max-width: 520px; max-height: calc(100vh - 40px); overflow-y: auto; }
.paywall-list { margin: 12px 0 0 18px; padding: 0; }
.paywall-list li { margin: 4px 0; }
/* Memory and disk picked with Embed App (issue #129): a slider per resource.
   The step names under a slider are spread to its ends so each sits under the
   position it names - the steps are evenly spaced, not to scale. */
.resource-picker { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--line); }
.resource-slider { margin: 12px 0 14px; }
.resource-slider-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 14px; }
.resource-slider-head label { font-weight: 600; }
.resource-slider input[type="range"] { width: 100%; margin: 8px 0 2px; accent-color: var(--primary); }
.resource-ticks { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-2); }

/* ── Preview / code ─────────────────────────────────────────────────────── */

.preview-frame { width: 100%; height: 620px; border: 1px solid var(--line); border-radius: var(--radius-md); background: #fff; display: block; margin-bottom: 16px; }
.stream-out {
  height: 320px; overflow: auto; background: #202124; color: #E8EAED; border-radius: var(--radius-md); padding: 16px;
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; white-space: pre-wrap; word-break: break-word;
}
/* A failed deploy, explained (deploy.js#diagnosisPanel, src/deploy/diagnose.js):
   the title and whose side it is on, the Dockerfile step, the few lines that
   say why, what to do. A left rule in the fault's colour, not a red box: most
   failures are not an alarm, they are a to-do. `.preflight` is the same look
   for the pre-check's "command-line tool" note in the setup form. */
.diag { margin-top: 16px; padding: 16px 18px; border: 1px solid var(--line); border-left: 4px solid var(--error); border-radius: var(--radius-sm); background: var(--bg-3); }
.diag-config, .diag-limit { border-left-color: var(--warning); }
.diag-transient, .diag-ours { border-left-color: var(--primary); }
.diag.preflight { margin-top: 10px; padding: 12px 14px; }
.diag.preflight .diag-detail { margin: 0 0 10px; }
.diag-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.diag-title { font-size: 15px; }
.diag-detail, .diag-fix { font-size: 14px; margin: 8px 0 0; }
.diag-step { margin: 8px 0 0; }
.diag-limit { margin: 8px 0 0; padding: 8px 12px; border-radius: var(--radius-sm); background: rgba(251, 188, 5, .14); }
.diag-step code { word-break: break-word; }
.diag-repeat { font-size: 13px; margin: 8px 0 0; color: var(--error); }
.diag-excerpt {
  margin: 10px 0 0; padding: 10px 12px; max-height: 220px; overflow: auto; background: #202124; color: #E8EAED; border-radius: var(--radius-sm);
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; white-space: pre-wrap; word-break: break-word;
}
.diag .stream-out { margin-top: 10px; }
.diag-log-toggle { display: block; margin-top: 12px; }
.badge.fault-repo, .badge.fault-config, .badge.fault-limit { background: rgba(251, 188, 5, .2); color: #8A6D00; }
.badge.fault-transient, .badge.fault-ours { background: rgba(66, 133, 244, .12); color: var(--primary); }
:root[data-theme="dark"] .badge.fault-repo, :root[data-theme="dark"] .badge.fault-config, :root[data-theme="dark"] .badge.fault-limit { color: var(--warning); }
.hint.warn { color: var(--text); }
.hint.warn::before { content: '⚠ '; color: #8A6D00; }
:root[data-theme="dark"] .hint.warn::before { color: var(--warning); }
/* The AI's Dockerfile changes (issue #85): each attempt of a fix run, and the
   fixed copy's changes in Source & build. Added lines green, removed red, on
   the same dark block as the logs. */
.fix-attempts { margin-top: 16px; }
.fix-attempt { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
.fix-attempt p { margin: 4px 0; }
.diff {
  margin: 8px 0 0; padding: 10px 12px; max-height: 280px; overflow: auto; background: #202124; color: #BDC1C6; border-radius: var(--radius-sm);
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; white-space: pre-wrap; word-break: break-word;
}
.diff .diff-add { color: #81C995; }
.diff .diff-del { color: #F28B82; }
.diff .diff-gap { color: #80868B; }

.linklike { background: none; border: 0; padding: 0; font: inherit; color: var(--primary); cursor: pointer; text-decoration: underline; }
.linklike.small { font-size: 13px; }

/* ── Settings the repository asks for (deploy.js#setupSection, issue #106) ─ */

.setup { margin: 4px 0 18px; }
.setup-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.setup-head h3 { font-size: 16px; margin: 0; }
.setup-group { margin: 16px 0 8px; font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-2); }
.setup-var { padding: 12px 14px; margin-bottom: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-2); scroll-margin-top: 80px; }
.setup-var.missing { border-left: 4px solid var(--warning); }
.setup-var.focus { box-shadow: 0 0 0 3px rgba(66, 133, 244, .35); }
.setup-var-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.setup-var-head code { font-size: 14px; font-weight: 700; word-break: break-all; }
.setup-var p { margin: 6px 0 0; }
.setup-input { display: flex; gap: 8px; align-items: center; margin-top: 8px; }
.setup-input input { flex: 1 1 auto; min-width: 0; }
.setup-meta { margin-top: 6px; font-size: 12px; color: var(--text-2); }
.setup-meta a { margin-right: 12px; }
/* `.upgrade-steps` is the same control for the memory and disk steps (issue
   #130) under its own name: deploy.js listens for `.pills` radios to fill the
   settings form, and a step picked there must not land in it. */
.pills, .upgrade-steps { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.pills label, .upgrade-steps label { position: relative; display: inline-flex; align-items: center; padding: 6px 12px; border: 1.5px solid var(--line); border-radius: 999px; font-size: 13px; cursor: pointer; }
.pills label:has(input:checked), .upgrade-steps label:has(input:checked) { border-color: var(--primary); background: rgba(66, 133, 244, .1); color: var(--primary); font-weight: 600; }
.pills label:has(input:focus-visible), .upgrade-steps label:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }
.pills input, .upgrade-steps input { position: absolute; opacity: 0; pointer-events: none; }
.upgrade-offer { margin-top: 10px; padding: 10px 12px; border-radius: var(--radius-sm); background: rgba(66, 133, 244, .07); }
.upgrade-offer > p { margin: 0; }
.upgrade-offer .upgrade-steps { margin: 8px 0; }
.resource-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 10px 0; border-top: 1px solid var(--line); }
.resource-row:first-of-type { border-top: 0; }

textarea.code { width: 100%; min-height: 520px; font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; line-height: 1.45; white-space: pre; overflow: auto; tab-size: 2; }
details.card > summary { cursor: pointer; list-style: none; }
details.card > summary::-webkit-details-marker { display: none; }
details.card > summary::before { content: '▸ '; color: var(--text-3); }
details.card[open] > summary::before { content: '▾ '; color: var(--primary); }

/* ── Design picker (design.js, issue #159) ───────────────────────────────── */

/* Each card is a sample of its design: its own background, heading font,
   colours and button radius come inline from the catalog; everything here is
   the frame around it, in the builder's own style. The radio is visually
   hidden but stays focusable - the frame shows focus and selection. */
.design-picker { margin: 8px 0 0; }
.design-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.dtab { border: 1.5px solid var(--line); background: var(--bg-2); color: var(--text-3); border-radius: 999px; padding: 6px 14px; font: inherit; font-size: 14px; font-weight: 500; cursor: pointer; transition: border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease); }
.dtab:hover { border-color: var(--primary); color: var(--primary); }
.dtab.on { border-color: var(--primary); background: rgba(66, 133, 244, .1); color: var(--primary); font-weight: 600; }
.design-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; max-width: 860px; }
.dcard { position: relative; display: flex; flex-direction: column; gap: 6px; cursor: pointer; min-width: 0; }
.dcard input { position: absolute; opacity: 0; pointer-events: none; }
.dprev { position: relative; display: flex; flex-direction: column; justify-content: space-between; gap: 10px; height: 104px; padding: 12px; border: 1px solid; border-radius: var(--radius-sm); overflow: hidden; box-shadow: var(--shadow-1); outline: 2px solid transparent; outline-offset: 2px; transition: outline-color .15s var(--ease), transform .15s var(--ease); }
.dcard:hover .dprev { transform: translateY(-2px); }
.dcard.on .dprev, .dcard:has(input:checked) .dprev { outline-color: var(--primary); }
.dcard:has(input:focus-visible) .dprev { outline-color: var(--primary); outline-style: dashed; }
.dcard:has(input:disabled) { cursor: not-allowed; opacity: .6; }
.dcard:has(input:disabled):hover .dprev { transform: none; }
.dname { font-size: 16px; line-height: 1.15; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.dfoot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ddots { display: flex; gap: 4px; flex: 1 1 auto; min-width: 0; overflow: hidden; }
.ddots i { flex: none; width: 12px; height: 12px; border-radius: 50%; box-shadow: 0 0 0 1px rgba(128, 128, 128, .35); }
.dbtn { font-size: 12px; font-weight: 600; padding: 4px 10px; white-space: nowrap; }
.dlabel { font-size: 13px; font-weight: 500; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dcard.on .dlabel { color: var(--primary); font-weight: 600; }
.dcard.auto .dprev { background: var(--bg-3); border: 1.5px dashed var(--line); color: var(--text); }
.dhint { font-size: 12px; color: var(--text-3); line-height: 1.3; }
.dprev.palette { flex-direction: row; gap: 0; padding: 0; border-color: var(--line); }
.dprev.palette i { flex: 1 1 0; }
@media (max-width: 479px) {
  .design-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
}

/* ── Workspace ──────────────────────────────────────────────────────────── */

.workspace-layout { display: grid; grid-template-columns: 240px 1fr; gap: 48px; align-items: start; }
@media (max-width: 767px) { .workspace-layout { grid-template-columns: 1fr; gap: 32px; } }
.projects { display: flex; flex-direction: column; gap: 8px; }
.project-row { display: flex; align-items: center; gap: 16px; padding: 10px 12px; margin: 0 -12px; border-radius: var(--radius-sm); transition: background .15s var(--ease); }
.project-row:hover { background: var(--bg-3); }
.project { font-size: 16px; font-weight: 500; color: var(--text); text-decoration: none; }
.project:hover { color: var(--primary); }
/* The website and app markers after a project's name (workspace.js#projectMarks).
   The name gives way (wraps) before the markers do, so a long repository name
   cannot push them out of the 240 px column. Amber is darker in the light theme
   than the --warning of badges: a thin glyph on white needs the contrast a
   filled pill does not. */
.project-row .project { min-width: 0; overflow-wrap: anywhere; }
.pmarks { display: inline-flex; gap: 6px; flex: none; margin-left: -6px; }
.pmark { display: inline-flex; color: var(--text-3); opacity: .55; }
.pmark svg { width: 16px; height: 16px; }
.pmark.ok { color: var(--success); opacity: 1; }
.pmark.warn { color: #B98300; opacity: 1; }
.pmark.err { color: var(--error); opacity: 1; }
:root[data-theme="dark"] .pmark.warn { color: var(--warning); }
.project-row .btn { opacity: 0; }
.project-row:hover .btn, .project-row:focus-within .btn { opacity: 1; }
.gen-row { display: flex; gap: 16px; align-items: center; }
.gen-row input { width: 600px; max-width: 100%; }
.btn.gen { width: 161px; padding: 0; flex: none; }
@media (max-width: 767px) { .gen-row { flex-wrap: wrap; } .btn.gen { width: 120px; } }
/* The Landing Page / Website wizard (workspace.js#wizardView): radio cards from
   .choice, then the page boxes indented under the Website card. */
.wizard { max-width: 600px; }
.wizard .choice { margin-bottom: 8px; }
.wizard-pages { padding: 4px 0 4px 14px; }
.wizard-pages .check { margin-bottom: 8px; }
/* "Authorize with GitHub" under the repository field (workspace.js#repoCheckLine):
   the mark of GitHub's own colour, the size of the other small buttons. */
.btn.github { color: var(--text); }
.btn.github svg { width: 16px; height: 16px; fill: currentColor; }

/* ── AI agents and API tokens (agent.js, account.js; issue #137) ───────── */

/* A reading column: the agent page is one card of text and a code, and at
   the full shell width its lines ran past what anyone reads in one go. */
.content.narrow { max-width: 760px; }
ol.steps { padding-left: 22px; margin: 0 0 12px; }
ol.steps li { margin: 8px 0; }
/* The code a person reads out to their agent: big, monospaced, spaced so
   eight letters do not blur into each other, and selectable by hand. */
.agent-code {
  display: inline-block; margin: 8px 0 20px; padding: 14px 22px; border: 1.5px dashed var(--line); border-radius: var(--radius-md);
  background: var(--bg-3); font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 34px; font-weight: 500; letter-spacing: .14em; user-select: all;
}
/* A new token, shown once: long and unbreakable, so it wraps anywhere. */
.secret-box { margin: 4px 0 16px; padding: 12px 14px; border: 1px solid var(--success); border-radius: var(--radius-sm); background: rgba(52, 168, 83, .06); }
.secret-box .hint { margin: 0 0 8px; }
.secret-box code { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; font-size: 13px; user-select: all; }
.token-form { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; margin-top: 16px; }
.token-form .field { margin-bottom: 0; }
.token-form .grow { flex: 1 1 260px; }
.token-form select { width: 140px; }

/* ── Auth page ──────────────────────────────────────────────────────────── */

.auth { display: flex; min-height: 100vh; }
.auth-left { flex: 8; display: flex; flex-direction: column; align-items: center; padding: 40px 16px 32px; }
.auth-col { width: 100%; max-width: 480px; }
.auth-col .logo { width: 220px; margin: 0 0 48px; }
.auth-right { flex: 6; background-size: cover; background-position: center; position: relative; }
.auth-right::after { content: ''; position: absolute; inset: 0; background: linear-gradient(160deg, rgba(66, 133, 244, .35), rgba(52, 168, 83, .25)); }
.tabs { display: flex; gap: 0; margin-bottom: 20px; border-bottom: 1.5px solid var(--line); }
.tab { font-size: 22px; padding: 12px 16px; color: var(--text-3); text-decoration: none; border: 0; background: none; cursor: pointer; font-family: inherit; font-weight: 500; border-bottom: 2.5px solid transparent; margin-bottom: -1.5px; transition: color .15s var(--ease); }
.tab.active { color: var(--primary); border-bottom-color: var(--primary); font-weight: 700; }
.auth-lead { font-size: 14px; margin: 0 0 20px; color: var(--text-3); }
.auth-actions { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-top: 16px; }
.auth-or { font-size: 14px; margin: 4px 0; color: var(--text-3); }
.btn.google { height: 44px; font-size: 14px; font-weight: 600; border-width: 1.5px; }
.btn.google svg { width: 20px; height: 20px; }
.btn.textlink { border-color: transparent; background: transparent; height: 44px; font-size: 14px; font-weight: 500; color: var(--primary); }
.auth-msg { font-size: 14px; margin: 0 0 12px; }
.auth-hint { font-size: 13px; line-height: 1.5; margin: 20px 0 0; color: var(--text-3); text-align: center; }

@media (max-width: 991px) {
  .auth-right { display: none; }
  .logo { width: 160px; }
}
@media (max-width: 820px) {
  .shell.with-side { grid-template-columns: 1fr; }
  .sidebar { position: static; }
}
@media (max-width: 479px) {
  :root { --gutter: 16px; }
  .topbar-in { padding: 8px var(--gutter); min-height: 56px; }
  .topbar .logo, .topnav .nav, .topnav .who, .topnav .account-menu { display: none; }
  .menu-btn { display: inline-flex; }
  .content { padding-top: 40px; }
  .page-title { font-size: 28px; margin-bottom: 20px; }
  .section-title { font-size: 20px; }
  .page-head { margin-bottom: 20px; }
  .footer { margin-top: 64px; }
  .footer .logo, .footer .gh { display: none; }
  /* GitHub's icon is dropped on a phone (the row is crowded), but the
     community server is the one link here a phone visitor can act on right
     away, so Discord stays, smaller. */
  .footer .gh.discord { display: inline-flex; }
  .footer .gh.discord svg { width: 28px; height: 28px; }
  .footer .copy, .footer .mail { font-size: 14px; }
}
