/* ==========================================================================
   extensionbot.org styles.

   Section A is the <style> block from Homestead's /search page (homestead_app.py), copied
   verbatim apart from the two edits marked "deviation" below. Plain CSS, no framework, no
   fonts, no images. Colors are the eight variables on :root; dark mode follows
   prefers-color-scheme. If homestead_app.py's style block changes, re-copy Section A rather
   than re-interpreting it — see docs/STYLE.md.

   Section B is this app's own chrome, which the /search page has no equivalent for (it is a
   single bare column with no nav): the top navigation, the recent-conversations sidebar, the
   admin tables, the flash, and the auth/settings form scaffolding. Everything there uses only
   the eight tokens, so it themes with Section A instead of fighting it.
   ========================================================================== */

/* ========================================================================
   Section A — homestead_app.py, verbatim
   ======================================================================== */

:root {
  --bg: #ffffff; --fg: #1a1a1a; --muted: #5f6368; --border: #dadce0;
  --card: #ffffff; --accent: #1a5e9a; --error: #b00020; --input-bg: #ffffff;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #15171a; --fg: #e8eaed; --muted: #9aa0a6; --border: #3c4043;
    --card: #1e2125; --accent: #8ab4f8; --error: #f28b82; --input-bg: #202327;
  }
}
* { box-sizing: border-box; }

/* Deviation: not in homestead_app.py. The ask UI shows and hides every live region (the status
   line, the error, a turn's sources and retrieval summary) with the `hidden` *attribute*, from
   ask_controller.js. Section B sets `display` on some of those same elements, so without this
   they would stay visible when hidden. Don't override [hidden] with a display rule anywhere. */
[hidden] { display: none !important; }

body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
       background: var(--bg); color: var(--fg); margin: 0; }
main { max-width: 780px; margin: 0 auto; padding: 2rem 16px; }
h1 { font-size: 1.4rem; margin: 0 0 0.25rem; }
p.sub { color: var(--muted); margin: 0 0 1.5rem; font-size: 0.9rem; line-height: 1.4; }
form { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.75rem; }
textarea#q { flex: 1 1 100%; padding: 0.6rem; font-size: 1rem; font-family: inherit;
             resize: vertical; min-height: 2.6rem; background: var(--input-bg); color: var(--fg);
             border: 1px solid var(--border); border-radius: 6px; }
button#tab-hint { flex: 1 1 100%; background: none; border: 0; padding: 0; margin: -0.25rem 0 0;
                  font-weight: normal; font-size: 0.8rem; color: var(--muted); text-align: left; }
button#tab-hint:hover { color: var(--accent); }
kbd { font: inherit; font-size: 0.75rem; padding: 0.05rem 0.4rem; border: 1px solid var(--border);
      border-bottom-width: 2px; border-radius: 4px; background: var(--input-bg); }
select, button { padding: 0.45rem 0.6rem; font-size: 0.95rem; background: var(--input-bg);
                 color: var(--fg); border: 1px solid var(--border); border-radius: 6px; }
button { cursor: pointer; background: var(--accent); color: var(--bg); border-color: var(--accent);
         font-weight: 600; padding: 0.45rem 1.1rem; }
button.secondary { background: var(--input-bg); color: var(--fg); border-color: var(--border); }
/* Deviation: not in homestead_app.py. Some actions here are links where the reference page's are
   buttons — "Ask a question" on the About page, "New chat" under an anonymous thread — and both
   should look the same. */
a.secondary, a.primary { display: inline-block; padding: 0.45rem 1.1rem; font-size: 0.95rem;
                         border: 1px solid var(--border); border-radius: 6px; font-weight: 600;
                         text-decoration: none; }
a.secondary { background: var(--input-bg); color: var(--fg); }
a.primary { background: var(--accent); color: var(--bg); border-color: var(--accent); }
label { font-size: 0.85rem; color: var(--muted); display: flex; align-items: center; gap: 0.3rem; }
.spacer { flex: 1 1 auto; }
.result { background: var(--card); border: 1px solid var(--border); border-radius: 8px;
          padding: 0.75rem 1rem; margin-bottom: 0.75rem; }
.result .meta { font-size: 0.8rem; color: var(--muted); margin-bottom: 0.25rem;
                font-family: ui-monospace, Menlo, monospace; word-break: break-all; }
.result .title { font-weight: 600; }
.result .state { font-size: 0.8rem; color: var(--muted); margin-left: 0.4rem; font-weight: normal; }
.result a { font-size: 0.85rem; word-break: break-all; color: var(--accent); }
.result .content { margin-top: 0.5rem; white-space: pre-wrap; font-size: 0.9rem; line-height: 1.45; }
.result.flash { outline: 2px solid var(--accent); }
.badge { font-size: 0.75rem; color: var(--accent); border: 1px solid var(--accent); border-radius: 4px;
         padding: 0 0.3rem; margin-left: 0.4rem; font-weight: normal; white-space: nowrap; }
.answer { border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: 8px;
          background: var(--card); padding: 0.75rem 1rem; margin-bottom: 1.25rem; }
.answer h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted);
             margin: 0 0 0.4rem; }
.answer .text { line-height: 1.5; }
.answer .text a.cite { color: var(--accent); text-decoration: none; font-size: 0.85em; font-weight: 600; }
.answer .text a.cite:hover { text-decoration: underline; }
.answer .cited { margin: 0.75rem 0 0; padding-top: 0.5rem; border-top: 1px solid var(--border); font-size: 0.85rem; }
.answer .cited h3 { font-size: 0.85rem; margin: 0 0 0.3rem; }
.answer .cited ol { list-style: none; margin: 0; padding: 0; }
.answer .cited li { margin-bottom: 0.3rem; overflow-wrap: anywhere; }
.answer .cited a { color: var(--accent); }
.answer .note { color: var(--muted); font-size: 0.8rem; margin: 0.6rem 0 0; }
.answer .warn { color: var(--error); }
.error { color: var(--error); }
.status { color: var(--muted); font-size: 0.9rem; margin: 0.75rem 0; min-height: 1.2em; }
.sources-heading { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted);
                   margin: 0 0 0.5rem; }
.turn { margin-bottom: 1.25rem; }
.turn .question { background: var(--input-bg); border: 1px solid var(--border); border-radius: 8px;
                  padding: 0.5rem 0.8rem; margin: 0 0 0.6rem; font-weight: 600; white-space: pre-wrap;
                  overflow-wrap: anywhere; }
.turn .searched { color: var(--muted); font-size: 0.8rem; margin: 0 0 0.6rem; display: none; }
.turn .answer { margin-bottom: 0.75rem; }
.turn .turn-error { margin: 0 0 0.6rem; }

/* Deviation: homestead_app.py toggles `body.details` from JS and remembers it in localStorage.
   Here "show details" is a bare checkbox with no JS at all, so the reveal keys off :checked
   directly. Scoped to body (not the ask page) so it also reaches a turn appended by
   ask_controller.js after the box was already ticked. */
body:has(#show-details:checked) .turn .searched,
body:has(#show-details:checked) .source-detail,
body:has(#show-details:checked) .retrieval-summary { display: block; }

/* ========================================================================
   Section B — this app's own chrome. Tokens only, no new colors.
   ======================================================================== */

/* ------------------------------------------------------------- The accent */

/* The one token this app does not take from Homestead. Section A's accent is the Google-ish blue
   that came with the /search page's Material palette, and nothing else on this site is blue: every
   page ends in the footer's full-bleed Extension Orange. Two unrelated accents, so the accent
   becomes the footer's hue instead — darkened until it can carry text, since --ef-orange (#ff7300)
   is only 2.7:1 against white and cannot be a fill. #a64600 is 6.0:1 on white, #ffa05c is 8.9:1 on
   the dark background, as fill (with --bg as the text) and as link text both.

   It lives here, not in Section A's :root, so re-copying Section A doesn't quietly put the blue
   back. The footer keeps its own --ef-orange: that band is the Foundation's, not a third accent. */
:root { --accent: #a64600; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --accent: #ffa05c; }
}

/* ----------------------------------------------------------- Retried asks */

/* Opens an answer the app went back out for when the home state had nothing to offer. It has to
   be read before the prose under it — that prose is about the whole country, not the state that
   was asked for — so it is a tinted band inside the card rather than the muted footnote treatment
   of .answer .note. No left stripe: the card it sits in already carries the accent border. */
.answer .retried { color: var(--fg); font-size: 0.9rem; line-height: 1.45; font-weight: 500;
                   background: var(--input-bg); border: 1px solid var(--border); border-radius: 6px;
                   padding: 0.5rem 0.7rem; margin: 0 0 0.7rem; }

/* ------------------------------------------------------------- The ask box */

/* The form's submit. The reference page ends its control row with an accent "Ask" pill; here that
   same accent fill is a circle holding an up arrow, the way every chat box marks "send". The arrow
   is one of the app's icons (IconHelper), stroked in currentColor, so it themes with the button. */
.ask-send { flex: none; display: inline-flex; align-items: center; justify-content: center;
            width: 2.3rem; height: 2.3rem; padding: 0; border-radius: 50%; }
.ask-send__icon { width: 1.2rem; height: 1.2rem; }
/* ask_controller.js disables it for the length of an answer. */
.ask-send:disabled { opacity: 0.55; cursor: default; }

/* The composer collapses while an answer streams: the question is already in the thread above, so
   the box would be showing it twice, and one that still looks typeable invites a second ask the
   controller can't run (this.turn is a single slot). display:none can't animate a height collapse —
   it fades and then the layout snaps — so this is the grid 1fr/0fr technique. That needs a
   container, which is what .ask-composer is for; .ask-composer__inner re-establishes the flex row
   the controls used to get from the <form> directly, and .status and .error stay outside both so
   they survive the collapse. `inert` is the state: ask_controller sets it, which takes the controls
   out of the tab order and the accessibility tree as well as styling them.
   The padding and the negative margin that cancels it are the room the focus ring needs: the
   overflow above has to clip, and a :focus-visible outline sits 2px outside the control and is 2px
   wide, so without it the textarea's ring — the control that spans the full width — was sliced off
   down both sides. Equal and opposite, so nothing moves and the collapse still lands at zero. */
.ask-composer { flex: 1 1 100%; display: grid; grid-template-rows: 1fr;
                transition: grid-template-rows 240ms ease, opacity 160ms ease; }
.ask-composer__inner { display: flex; flex-wrap: wrap; gap: 0.5rem; min-height: 0; overflow: hidden;
                       margin: -0.375rem; padding: 0.375rem; }
.ask-composer[inert] { grid-template-rows: 0fr; opacity: 0; }

/* The status line and a freshly streamed turn fade in rather than pop, so submitting reads as one
   gesture rather than three things happening at once. .turn--entering is added by ask_controller,
   so the turns already on the page at load don't animate. */
@keyframes ask-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ask-turn-in { from { opacity: 0; transform: translateY(6px); }
                         to { opacity: 1; transform: none; } }
.status:not([hidden]) { animation: ask-fade-in 200ms ease; }
.turn--entering { animation: ask-turn-in 260ms ease both; }

@media (prefers-reduced-motion: reduce) {
  .ask-composer { transition: none; }
  .status:not([hidden]), .turn--entering { animation: none; }
}

/* ----------------------------------------------------------- Select fields */

/* Section A gives <select> the same box as the other inputs and then leaves the control itself to
   the browser: a system chevron on a system plate, sized and coloured by the OS rather than by this
   page. It is the one thing in the ask row that doesn't look drawn by this stylesheet, so the native
   widget comes off and the caret is redrawn here — two gradients meeting in a triangle, which needs
   no image file and no icon set, and takes var(--muted) so it themes with everything else. The
   padding on the right is the room the caret sits in. */
select { appearance: none; -webkit-appearance: none; cursor: pointer; padding-right: 2rem;
         background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                           linear-gradient(135deg, var(--muted) 50%, transparent 50%);
         background-position: calc(100% - 1.05rem) calc(50% + 0.05rem),
                              calc(100% - 0.75rem) calc(50% + 0.05rem);
         background-size: 0.3rem 0.3rem; background-repeat: no-repeat;
         transition: border-color 120ms ease; }
select:hover { border-color: var(--muted); }
/* In the ask row the state picker is a control, not a form field: it sits beside a round send button
   and under the same nav that spells its links as pills, so it takes the pill too. Only here — the
   selects on Settings and sign-up are fields in a column of fields, and keep the 6px corners. */
.ask-composer__inner select { border-radius: 999px; padding-left: 0.9rem; }

/* ------------------------------------------------------------------ Layout */

/* The /search page puts these on <main>; this app's pages are .page, and every signed-in page nests
   .page inside .app-shell__main, beside the conversations rail. The column is wider than the
   reference's 780px: the ask box and an answer's sources read better with the extra room. It is the
   app's one column width: .nav, .flash and the footer's two inners all carry the same number, so the
   nav brand, the heading, the ask box and the footer marks share a left edge. Move all five together. */
.page { max-width: 1024px; margin: 0 auto; padding: 2rem 16px; }
/* The ask page before anything has been asked: heading and box sit in the middle of the column
   rather than hugging the nav. Once a thread exists the page is top-aligned like every other. */
.page--hero { min-height: calc(100vh - 12rem); display: flex; flex-direction: column;
              justify-content: center; }
/* The signed-out front page says the same thing, but as a masthead rather than a chat landing: it
   stays at the top of the column, with a little more air under the nav than .page's own padding. */
.page--intro { padding-top: 4rem; }

/* The ask page's masthead: the mascot, then the question as real text so it themes with the page
   and reads as a heading. The PNG is transparent, so it needs no plate behind it on either theme. */
.ask-title { display: flex; align-items: center; justify-content: center; gap: 0.6rem;
             margin: 0 0 0.5rem; font-size: 1.75rem; }
.ask-title__mascot { width: auto; height: 2.6rem; flex: none; }
/* The line under a centred masthead is part of it, so it centres too — only this one, which is
   why it is reached through the heading rather than by styling .sub itself. */
.ask-title + .sub { text-align: center; }

h2, h3 { margin: 1.5rem 0 0.5rem; }
h2 { font-size: 1.1rem; }
h3 { font-size: 0.95rem; }
p { line-height: 1.5; }
a { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A heading element carrying .question is still a question box, not a heading. */
.turn .question { font-size: 1rem; }

/* The /search page labels its textarea with a placeholder alone. This app keeps a real label,
   which needs its own row above the box. */
form label[for="q"] { flex: 1 1 100%; margin-bottom: -0.25rem; }

/* Raw Markdown streams in as text, so newlines are all it has until the rendered HTML lands
   in finish() — at which point real <p> elements carry the spacing and pre-wrap would double it. */
.answer .text[data-streaming] { white-space: pre-wrap; }
.answer .text > :first-child { margin-top: 0; }
.answer .text > :last-child { margin-bottom: 0; }

/* --------------------------------------------------------- Top navigation */

.site-header { border-bottom: 1px solid var(--border); background: var(--bg); }
.nav { max-width: 1024px; margin: 0 auto; padding: 0.6rem 16px; display: flex; flex-wrap: wrap;
       align-items: center; gap: 0.3rem 1rem; }
/* The brand is the mark itself. The admin bar reuses .nav__brand for a bare <span>, which the
   font-weight and color still cover. */
.nav__brand { display: inline-flex; font-weight: 600; color: var(--fg); text-decoration: none;
              margin-right: auto; }
.nav__brand:hover { color: var(--accent); }
.nav__brand img { display: block; width: auto; height: 2.1rem; }
.nav__brand:hover img { opacity: 0.85; }
.nav__links { list-style: none; display: flex; flex-wrap: wrap; gap: 1rem; margin: 0; padding: 0;
              font-size: 0.9rem; }
.nav__links a { color: var(--muted); text-decoration: none; }
.nav__links a:hover, .nav__links a[aria-current="page"] { color: var(--accent); }
.nav__signout-form { display: contents; }
/* Sign in and sign up are pills rather than bare links: they are the two things to do here, and the
   bar's other links are navigation. Filled uses --fg over --bg, so it is dark on light and light on
   dark without a color of its own. The pills sit in the same <ul> as the navigation links, so every
   rule here has to out-rank `.nav__links a` above — a class and a type selector, which a lone
   `.nav__btn` loses to. Keep the `.nav__links a` prefix on all four, or the pills go grey. */
.nav__links a.nav__btn { display: inline-block; padding: 0.35rem 0.9rem; border: 1px solid var(--border);
            border-radius: 999px; color: var(--fg); text-decoration: none; line-height: 1.3; }
.nav__links a.nav__btn:hover { border-color: var(--fg); color: var(--fg); }
.nav__links a.nav__btn--solid { background: var(--fg); border-color: var(--fg); color: var(--bg); }
.nav__links a.nav__btn--solid:hover { color: var(--bg); opacity: 0.85; }
/* Dark theme: --fg as the fill would make this the brightest thing on the page, and its text would
   turn dark. Keep it the dark pill with light text that it is on a light background, and lean on a
   brighter border than the outlined pill's to keep the two apart. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .nav__btn--solid {
    background: var(--input-bg); border-color: var(--muted); color: var(--fg);
  }
  :root:not([data-theme="light"]) .nav__btn--solid:hover { color: var(--fg); }
}

.nav__user { color: var(--muted); font-size: 0.9rem; }
.nav__signout { background: none; border: 0; padding: 0; font-weight: normal; font-size: 0.9rem;
                color: var(--muted); }
.nav__signout:hover { color: var(--accent); }
/* The 1024px nav lines up with the content column on a one-column page. With the sidebar there is no
   single centred column to line up with, so the bar spans the window above both. */
.site-header--shell .nav { max-width: none; }
.nav--admin { background: var(--input-bg); }
.nav--admin .nav__brand { font-size: 0.9rem; color: var(--muted); }

/* ------------------------------------------------- App shell with sidebar */

/* A rail down the left edge of the viewport and the 1024px column centred in what's left. The rail
   is the only way to reach past conversations (there is no history page), so it sticks to the top
   and scrolls on its own however long the list is. */
.app-shell { display: flex; align-items: flex-start; }
.app-shell__main { flex: 1 1 auto; min-width: 0; }
.conversations-sidebar { flex: 0 0 16rem; position: sticky; top: 0; height: 100vh;
                         overflow-y: auto; border-right: 1px solid var(--border);
                         padding: 2rem 1rem; font-size: 0.85rem; }
/* "+ New": the rail's first row, and the only way to start a conversation from a page that is
   already in one. It highlights on hover like the conversations under it. */
.new-chat { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.35rem 0.5rem;
            margin: 0 -0.5rem 1rem; border-radius: 6px; color: var(--fg); text-decoration: none; }
.new-chat:hover { background: var(--input-bg); color: var(--fg); }
.new-chat__plus { color: var(--muted); }
.new-chat:hover .new-chat__plus { color: var(--fg); }
.conversations-sidebar__heading { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em;
                                  color: var(--muted); margin: 0 0 0.5rem; }
.conversations-sidebar__empty { color: var(--muted); font-size: 0.85rem; }
.conversation-list { list-style: none; margin: 0 0 0.5rem; padding: 0; }
.conversation-list__item { display: block; padding: 0.4rem 0.5rem; margin: 0 -0.5rem; border-radius: 6px;
                           color: var(--fg); text-decoration: none; }
.conversation-list__item:hover { background: var(--input-bg); }
.conversation-list__item[aria-current="page"] { background: var(--input-bg); font-weight: 600; }
.conversation-list__title { display: block; overflow-wrap: anywhere; }
.conversation-list__meta { display: block; color: var(--muted); font-size: 0.75rem; margin-top: 0.1rem; }
.conversations-sidebar__archived { display: inline-block; margin-top: 0.75rem; color: var(--muted);
                                  font-size: 0.8rem; }

/* A conversation's row: the link to it, and the "…" that opens its menu. The trigger keeps out of
   the way until the row is hovered or something inside it has focus — but a touch screen never
   hovers, so there it simply stays visible. */
.conversation-row { display: flex; align-items: flex-start; gap: 0.25rem; }
.conversation-row .conversation-list__item { flex: 1 1 auto; min-width: 0; }
.conversation-row__trigger { flex: none; background: none; border: 0; border-radius: 6px;
                             color: var(--muted); line-height: 1; padding: 0.3rem 0.4rem; }
.conversation-row__trigger:hover { background: var(--input-bg); color: var(--fg); }
@media (hover: hover) {
  .conversation-row__trigger { opacity: 0; }
  .conversation-row:hover .conversation-row__trigger,
  .conversation-row:focus-within .conversation-row__trigger { opacity: 1; }
}

/* The row menu. The reference page has no menus at all, so this is the app's one floating surface:
   flat like every other card here — a border, no shadow. It is a native popover, which puts it in
   the top layer (the rail's own overflow would otherwise clip it); row_menu_controller.js sets the
   left/top that a popover has no way to work out for itself. */
.row-menu[popover] { position: fixed; inset: auto; margin: 0; width: 15rem; padding: 0.3rem;
                     background: var(--card); color: var(--fg); border: 1px solid var(--border);
                     border-radius: 8px; font-size: 0.85rem; }
.row-menu__item { display: flex; align-items: center; gap: 0.55rem; width: 100%;
                  padding: 0.45rem 0.6rem; border: 0; border-radius: 6px;
                  background: none; color: var(--fg); font-size: 0.85rem; font-weight: normal;
                  text-align: left; text-decoration: none; }
.row-menu__item:hover { background: var(--input-bg); }
.row-menu__item--danger { color: var(--error); }
.row-menu__divider { border: 0; border-top: 1px solid var(--border); margin: 0.3rem 0; }
.row-menu__rename { display: block; margin: 0; padding: 0.45rem 0.6rem 0.2rem; }
.row-menu__label { display: flex; align-items: center; gap: 0.55rem; margin-bottom: 0.25rem;
                   font-size: 0.85rem; }
.row-menu__input { display: block; width: 100%; padding: 0.35rem 0.5rem; font-size: 0.85rem;
                   font-family: inherit; background: var(--input-bg); color: var(--fg);
                   border: 1px solid var(--border); border-radius: 6px; }
input[type="submit"].row-menu__save { margin-top: 0.4rem; padding: 0.3rem 0.8rem; font-size: 0.8rem;
                                      background: var(--input-bg); color: var(--fg);
                                      border-color: var(--border); }

/* Too narrow for two columns: the ask box comes first and the rail stacks under it, capped so a long
   list doesn't bury the page. Hiding it would leave a phone with no way back to a conversation. */
@media (max-width: 54rem) {
  .app-shell { flex-direction: column; }
  .conversations-sidebar { order: 2; flex: none; width: 100%; position: static; height: auto;
                           max-height: 22rem; border-right: 0; border-top: 1px solid var(--border); }
  .page--hero { min-height: 0; }
}

/* ---------------------------------------------------------------- Footer */

/* The Extension Foundation's footer, reproduced from extension.org. It is the one part of this app
   that is not in Homestead's idiom: a full-bleed Extension Orange band with the Foundation's marks
   on it, then a black copyright bar. The two colors are scoped to .site-footer rather than added to
   :root, so the eight tokens still describe the whole of the rest of the app and nothing else can
   reach a second accent. The band is the same in light and dark mode on purpose — the marks are
   black-on-transparent PNGs, and an orange plate is what keeps them legible either way. See "The
   footer" in docs/STYLE.md before changing any of this. */
.site-footer { --ef-orange: #ff7300; --ef-ink: #111111; --ef-bar: #0d0d0d;
               background: var(--ef-orange); color: var(--ef-ink); font-size: 0.9rem;
               line-height: 1.45; }
/* Three columns in the same 1024px column as .page, .nav and .flash. The band is full-bleed, so its
   content width is a free choice, but sharing the number lines the Extension mark here up with the one
   in the nav. The copyright bar below uses it too — move all of them together. */
.site-footer__inner { max-width: 1024px; margin: 0 auto; padding: 1.75rem 16px 1.5rem;
                      display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem 3rem; }
.site-footer__col p { margin: 0 0 1rem; }
.site-footer__col p:last-child { margin-bottom: 0; }
/* The three marks are nothing like each other in shape — the two wordmarks are about 3.8:1, the
   NIFA lockup about 10.8:1 — so sizing them by width alone would leave NIFA a third the height of
   its neighbours and start this column's text well above the other two. Each one is scaled to fit
   a slot of the same height instead and sat on a common baseline, which lines up the three columns
   and is shorter than the width-sized marks were. A swapped file of any proportion still fits. */
.site-footer__mark { display: block; width: 100%; max-width: 17rem; height: 3.5rem;
                     object-fit: contain; object-position: left bottom; margin-bottom: 1rem; }
.site-footer__mark--nifa { max-width: 19rem; }
/* The grant acknowledgement is italic on extension.org, and it is the small print here too. */
.site-footer__grant { font-style: italic; font-size: 0.75rem; }

.site-footer a { color: var(--ef-ink); text-decoration: underline; }
.site-footer a:hover { text-decoration: none; }

/* The copyright bar: the band's black foot, full width like the band above it. */
.site-footer__bar { background: var(--ef-bar); color: #ffffff; }
.site-footer__bar-inner { max-width: 1024px; margin: 0 auto; padding: 1rem 16px;
                          display: flex; flex-wrap: wrap; gap: 0.3rem 1.5rem;
                          align-items: baseline; justify-content: space-between; }
.site-footer__bar a { color: #ffffff; }

/* The footer's own breakpoints — its columns run out of room before the app shell's rail does.
   Three across on a desktop; on a tablet the grant drops beneath the other two and spans them,
   where it costs only a few lines; everything stacks on a phone. The span is harmless once there
   is one column, so it needs no reset. */
@media (max-width: 60rem) {
  .site-footer__inner { grid-template-columns: 1fr 1fr; }
  .site-footer__col--grant { grid-column: 1 / -1; }
}
@media (max-width: 40rem) {
  .site-footer__inner { grid-template-columns: 1fr; gap: 2rem; }
}

/* ------------------------------------------------------------------ Flash */

.flash { max-width: 1024px; margin: 0 auto; padding: 0.75rem 16px 0; }
.flash p { margin: 0; font-size: 0.9rem; }
.alert { color: var(--error); }
.notice { color: var(--muted); }

/* --------------------------------------------------------- Form scaffolding */

/* Rails' form helpers emit <input type="submit">, which homestead_app.py never does — give it
   the same look as the reference's <button>. */
input[type="submit"] { cursor: pointer; padding: 0.45rem 1.1rem; font-size: 0.95rem;
                       font-family: inherit; background: var(--accent); color: var(--bg);
                       border: 1px solid var(--accent); border-radius: 6px; font-weight: 600; }

/* The /search page is one flex form of bare controls. These pages (sign in, settings) are
   stacked label/field pairs instead, so they opt out of that flex row. */
.form-stacked { display: block; }
.field { margin-bottom: 1rem; }
.field label { margin-bottom: 0.25rem; }
.field input[type="email"], .field input[type="password"], .field input[type="text"],
.field select, .field textarea {
  display: block; width: 100%; max-width: 24rem; padding: 0.45rem 0.6rem; font-size: 0.95rem;
  font-family: inherit; background-color: var(--input-bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: 6px;
}
/* Only the admin's library editor has a textarea in a stacked form, and it is editing an answer:
   24rem is too narrow to read prose in, and resizing is the one control that page wants. */
.field textarea { max-width: 48rem; resize: vertical; }
/* background-color, not the background shorthand, in the rule above: the shorthand would drop the
   caret these selects draw as a background-image. The padding is restated for the same reason — the
   shared one above is the input padding, which would leave the text running under the caret. */
.field select { padding-right: 2rem; }
.field-hint { color: var(--muted); font-size: 0.8rem; margin: 0.25rem 0 0; }
.required { color: var(--error); }
.stack { display: flex; flex-direction: column; gap: 1rem; align-items: flex-start; }
.page-actions { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border);
                font-size: 0.85rem; }
.form-contents { display: contents; }
.empty-state { border: 1px dashed var(--border); border-radius: 8px; padding: 2rem; text-align: center;
               color: var(--muted); }

/* ----------------------------------------------------------------- Tables */

.table-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px;
                background: var(--card); }
table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
th, td { text-align: left; padding: 0.6rem 1rem; border-bottom: 1px solid var(--border);
         vertical-align: top; }
th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted);
     font-weight: 600; }
tbody tr:last-child td { border-bottom: 0; }
.stat-table th { text-transform: none; letter-spacing: normal; font-size: 0.9rem; width: 14rem;
                 color: var(--fg); font-weight: normal; }
.stat-table td { font-variant-numeric: tabular-nums; font-weight: 600; }

/* -------------------------------------------------------- Lists and detail */

.result__row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.result .title a { font-size: 1rem; color: var(--fg); text-decoration: none; }
.result .title a:hover { color: var(--accent); text-decoration: underline; }
.result .content { margin-bottom: 0.5rem; }
/* .meta is the reference's monospace debug line (scores, hashes). A timestamp is prose. */
.result .when { color: var(--muted); font-size: 0.8rem; margin-top: 0.25rem; }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }

/* Icons (IconHelper). :where() gives .icon no specificity of its own, so any sized class on the same
   svg — .ask-send__icon, .row-delete__icon — wins wherever it sits in the file. */
:where(.icon) { display: block; flex: none; width: 1.1em; height: 1.1em; }
/* A button or link that carries an icon beside its words. */
.with-icon { display: inline-flex; align-items: center; gap: 0.45rem; }
/* The favorite star on a cited source: an icon alone, muted until hovered, in the accent once the
   source is a favorite — and filled, since an outline reads as "not yet". */
.favorite { background: none; border: 0; border-radius: 6px; color: var(--muted); line-height: 1;
            padding: 0.15rem 0.3rem; vertical-align: middle; }
.favorite:hover { background: var(--input-bg); color: var(--accent); }
.favorite--on { color: var(--accent); }
.favorite--on .icon { fill: currentColor; }

/* The reference only ever shows a state code on a .result card; here one also follows a cited
   source's title and a favorite's title. */
.answer .cited .state { color: var(--muted); font-weight: normal; }
td.meta { color: var(--muted); font-size: 0.8rem; white-space: nowrap; }

/* The All questions table's one row action: the trash icon alone, so it needs a label of its own
   (the button has an aria-label and a title). It stays visible, unlike the rail's menu trigger: a
   table row is nowhere near as dense, and an action nobody can find is worse than one waiting
   quietly in its own column. */
.row-delete { background: none; border: 0; border-radius: 6px; color: var(--muted); line-height: 1;
              padding: 0.35rem 0.45rem; }
.row-delete__icon { width: 1.1rem; height: 1.1rem; }
.row-delete:hover { background: var(--input-bg); color: var(--error); }

.source-detail, .retrieval-summary { display: none; color: var(--muted); font-size: 0.8rem;
                                     margin: 0.2rem 0 0; }
