/* ============================================================
   THE SIGNED-IN SHELL — one panel, one bar, one phone strip.

   Loaded by every page that is INSIDE the product: /home, the store, the
   conversations, publishing, a profile, the account page. Loaded by nothing
   else. It is written LAST in the head, after assets/site.css or
   assets/app.css, so that where the two describe the same box this one wins
   and there is exactly one description of the shell on the whole site.

   ## Why this file exists at all

   There were two signed-in shapes, and moving between them swapped the whole
   chrome:

     "If I click on community, it takes me back to the top header view. If I
      click on home, I goes back to side panel. If I click common, I go back
      to this view. … There is no consistency of type of view."

   /home drew a panel from assets/app.css; every other signed-in address drew
   a top bar from assets/site.css. Two stylesheets described two shells, so
   the two shells drifted — which is what "no consistency" is, in files. The
   shell now lives in ONE file that both of them load, and the markup that
   goes with it in ONE generator (scripts/build-header.mjs). There is no
   second copy left to disagree.

   The two page stylesheets stay as they were: site.css draws the marketing
   pages and the content of the store, app.css draws the content of /home.
   They are two bodies of content with two jobs. This is only the frame.

   ## FIXED, not sticky

     "Now if I scroll, even my side panel goes up. See? Look at this. Why
      it's not sticky properly, not fixed properly."

   `position: fixed` on all three, and <body> is padded clear of them. A
   sticky box is still in the flow and still owned by whatever scrolls above
   it; a fixed box has no scroll position at all. The measure is applied to
   the content inside the padding, so the panel stays flush to the left edge
   of the window at 2560px exactly as it does at 1024.

   ## Tokens

   Every custom property below is defined by BOTH page stylesheets, in both
   themes, with the same values — --ink, --ink-2/3/4, --bg, --rail-bg,
   --surface, --line, --line-soft, --line-strong, --accent, --accent-soft,
   --shadow-1/2/3, --s-*, --t-*, --r-*, --dur, --ease, --font-mono. Nothing
   here defines a colour, so nothing here can disagree with the page it is
   framing, in either theme.

   The two that are the shell's own — how wide the panel is and how tall the
   bar is — are set here, and only here.
   ============================================================ */

:root {
  --rail-w: 236px;
  --topbar-h: 56px;
  --tabs-h: 58px;
}

/* ------------------------------------------------------------- on and off --

   The shell is in the markup of every product page and drawn on none of them
   until <html> says somebody is signed in. `data-session` is written before
   the first paint — by the server on a page a function rendered, by the flag
   cookie's script on a static one — so the correct frame is the first thing
   painted and the wrong one is never on screen for a frame.

   `display: none` rather than `hidden`: a panel that is not being used has to
   leave the accessibility tree as well as the picture, or a signed-out
   visitor reading a listing is offered a workspace they are not in.

   With JavaScript off and no cookie, nothing is set, and a product page is
   the marketing page it is to a stranger. That is the right way round: it is
   what every crawler gets, and it is the state the whole site works in. */

.rail,
.appbar,
.apptabs {
  display: none;
}

[data-session='in'] .in-app .rail,
[data-session='in'] .in-app .appbar {
  display: flex;
}

/*
  One search box per screen.

  The bar's box is how you reach the store from anywhere else on the site. The
  store page has its own, sitting with the filters whose counts it changes, and
  for a while both were on screen at once — same name, same job, and they
  disagreed: search from the top and the top box emptied while the one below
  kept your words. So on the page that owns a search, the bar does not draw one.

  `:has()` rather than a class on the body, because the body's classes are
  written by the header generator for every page at once. The rule names the
  situation instead of the page: any page that already carries a search field of
  its own does not get a second one in the bar. The store and the community were
  both doing it — on /community the two were searching the SAME list under two
  different placeholders.
*/
[data-session='in'] .in-app:has(input[name='q']:not(#shell-q)) .appsearch {
  display: none;
}

/* With the box gone the bar had nothing holding its right-hand end, so the
   theme control and the avatar sat marooned beside the panel with 900px of
   empty bar to their right. They belong at the end of the bar either way. */
[data-session='in'] .in-app:has(input[name='q']:not(#shell-q)) .appbar-right {
  margin-left: auto;
}

/*
  The marketing header and the five-column marketing footer, on a page inside
  the product.

    "Then on home page, I mean the footer, here we have again a footer. This
     page is not meant to be having a footer at all."
    "Like marketing page side, things should stay at marketing page. I mean,
     you know, all of these things, docs and kind of stuff, this is not meant
     to be like this."

  Both go. The panel is the navigation and the bar is the account; a workspace
  does not need a sitemap at the bottom of every screen. The one door back to
  the pages that sell the app is the quiet link at the foot of the panel.

  Scoped to `.in-app` on purpose. A marketing page keeps its header and its
  footer whether or not the reader is signed in — /features.html is the site
  that sells the app, and a workspace frame around it would be the same
  confusion pointing the other way.
*/
[data-session='in'] .in-app > .site-head,
[data-session='in'] .in-app > .site-foot {
  display: none;
}

[data-session='in'] .in-app {
  padding-left: var(--rail-w);
  padding-top: var(--topbar-h);
  background: var(--bg);
}

/* ------------------------------------------------------------- the panel -- */

.rail {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  z-index: 60;
  width: var(--rail-w);
  flex-direction: column;
  gap: var(--s-5);
  margin: 0;
  padding: var(--s-4) var(--s-3) var(--s-3);
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: var(--rail-bg);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.rail-brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2);
  border-radius: var(--r-md);
  color: inherit;
  text-decoration: none;
  transition: background var(--dur) var(--ease);
}

.rail-brand:hover {
  background: var(--line-soft);
  text-decoration: none;
}

.rail-brand-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--accent);
  color: var(--accent-fg);
}

.rail-brand-name {
  min-width: 0;
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rail-label {
  padding: 0 var(--s-2) var(--s-2);
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-4);
}

.rail-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  min-height: 36px;
  padding: 0 var(--s-2);
  border-radius: var(--r-md);
  font-size: var(--t-md);
  text-align: left;
  text-decoration: none;
  color: var(--ink-2);
  transition:
    background var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

.rail-item svg {
  flex: none;
  color: var(--ink-4);
  transition: color var(--dur) var(--ease);
}

.rail-item:hover {
  background: var(--line-soft);
  color: var(--ink);
  text-decoration: none;
}

.rail-item:hover svg {
  color: var(--ink-2);
}

.rail-item:active {
  background: var(--line);
}

.rail-item[aria-current='page'] {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  box-shadow: var(--shadow-1);
}

.rail-item[aria-current='page'] svg {
  color: var(--accent);
}

/* The marker sits in the panel's own left padding, not inside the row, so the
   selected place reads as attached to the edge of the window. */
.rail-item[aria-current='page']::before {
  content: '';
  position: absolute;
  left: -12px;
  top: 9px;
  bottom: 9px;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
}

.rail-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line-soft);
}

/*
  THE ONE PROFILE ENTRY.

    "Then if I click on profile, we have profile here. We have profile here
     too."

  It was in the panel and in the account menu at the same time. It is here,
  and the menu in the bar leads to settings and out — never back to a place the
  panel or the phone strip is already the way to.

  Served pointing at /account#profile — a page that exists and works with the
  scripts switched off — and upgraded by assets/site-account.js to the
  person's own /@handle, with their face and name on it, the moment
  /api/store/me answers. Neither state is a control that does nothing.
*/
.rail-you {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-2);
  border-radius: var(--r-md);
  text-align: left;
  text-decoration: none;
  color: inherit;
  transition: background var(--dur) var(--ease);
}

.rail-you:hover {
  background: var(--line-soft);
  text-decoration: none;
}

.rail-face {
  display: grid;
  place-items: center;
  flex: none;
  width: 28px;
  height: 28px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-3);
  font-size: var(--t-xs);
  font-weight: 600;
}

.rail-face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.rail-you-text {
  min-width: 0;
}

.rail-you-name {
  display: block;
  overflow: hidden;
  font-size: var(--t-sm);
  font-weight: 500;
  line-height: 1.25;
  color: var(--ink);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-you-handle {
  display: block;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  line-height: 1.4;
  color: var(--ink-3);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The one door back to the site that sells the app. Its header lists Features,
   Docs, Changelog, Roadmap, About and GitHub, so one link reaches all six and
   the workspace does not carry a marketing menu of its own. */
.rail-away {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2);
  border-radius: var(--r-md);
  font-size: var(--t-xs);
  text-decoration: none;
  color: var(--ink-4);
  transition:
    background var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

.rail-away:hover {
  background: var(--line-soft);
  color: var(--ink-2);
  text-decoration: none;
}

.rail-away svg {
  flex: none;
}

/* --------------------------------------------------------------- the bar -- */

.appbar {
  position: fixed;
  top: 0;
  right: 0;
  left: var(--rail-w);
  z-index: 50;
  align-items: center;
  gap: var(--s-3);
  min-height: var(--topbar-h);
  padding: 0 var(--s-5);
  border-bottom: 1px solid var(--line-soft);
  background: var(--bg);
}

/* The brand belongs to the panel. When the panel has gone, the bar carries it. */
.appbar-brand {
  display: none;
  align-items: center;
  gap: var(--s-2);
  color: inherit;
  text-decoration: none;
}

.appbar-brand-name {
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/*
  ONE SEARCH BOX, ONE TARGET.

  It searches the store, on every page, because the store is where most of
  this site's searchable text is and because a box that searches something
  different depending on which page you happen to be standing on is the same
  inconsistency in miniature. The conversations have their own search field on
  their own page. `assets/site-account.js` retargets this one to /community on
  the conversation pages, where the other answer is the obvious one, and moves
  the label with it so the words never say something the form does not do.

  A plain GET form: it works with the scripts switched off, and what it does
  is exactly what is written in it.
*/
.appsearch {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: 0 1 22rem;
  min-width: 0;
  height: 2rem;
  margin-left: auto;
  padding: 0 var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink-3);
  transition:
    border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}

.appsearch:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.appsearch svg {
  flex: none;
  color: var(--ink-4);
}

.appsearch input {
  width: 100%;
  min-width: 0;
  border: 0;
  outline: none;
  background: none;
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink);
}

/* Safari draws its own clear button inside a search field and it collides with
   the border radius at this height. */
.appsearch input::-webkit-search-decoration,
.appsearch input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

.appbar-right {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

/* The theme button, hidden until the theme script announces itself by putting
   `data-theme-choice` on <html>. A control that cannot work is not drawn. */
.shell-theme {
  display: none;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  transition:
    background var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

:root[data-theme-choice] .shell-theme {
  display: inline-flex;
}

.shell-theme:hover {
  background: var(--line-soft);
  color: var(--ink);
}

.shell-theme svg {
  display: none;
}

:root[data-theme-choice='system'] .shell-theme svg[data-icon='system'],
:root[data-theme-choice='light'] .shell-theme svg[data-icon='light'],
:root[data-theme-choice='dark'] .shell-theme svg[data-icon='dark'] {
  display: block;
}

/* ------------------------------------------------------ the account menu -- */

/*
  A <details>, so it opens with no script, and signing out is a real form post
  the server answers with a redirect. The marketing header's account control
  is drawn by a script into an empty slot; this one is served with the page,
  because a menu that appears a beat after everything else is the thing that
  control was already criticised for. The script fills in the name and the
  face. It does not create the menu.
*/
.appmenu {
  position: relative;
  flex: none;
}

.appmenu-btn {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  cursor: pointer;
  list-style: none;
}

.appmenu-btn::-webkit-details-marker {
  display: none;
}

.appmenu[open] .appmenu-btn .rail-face {
  border-color: var(--accent);
}

.appmenu-panel {
  position: absolute;
  top: calc(100% + var(--s-2));
  right: 0;
  z-index: 70;
  min-width: 13rem;
  padding: var(--s-1);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-3);
}

.appmenu-who {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--s-2) var(--s-3) var(--s-3);
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: var(--s-1);
}

.appmenu-name {
  overflow: hidden;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--ink);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.appmenu-handle {
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  color: var(--ink-4);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.appmenu-item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  min-height: 2.25rem;
  padding: 0 var(--s-3);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  font-size: var(--t-sm);
  text-align: left;
  text-decoration: none;
  color: var(--ink-2);
  cursor: pointer;
}

.appmenu-item svg {
  flex: none;
  color: var(--ink-4);
}

.appmenu-item:hover {
  background: var(--line-soft);
  color: var(--ink);
  text-decoration: none;
}

.appmenu-panel form {
  margin: 0;
}

/* ------------------------------------------------------- the phone strip -- */

/*
  Below 860px a 236px panel is a quarter of the screen spent on navigation, so
  the panel goes and the same four places come back along the bottom — the
  place a thumb reaches. They are the same four addresses, in the same order,
  marked the same way, from the same list in scripts/build-header.mjs, so the
  phone is the desktop with the frame moved rather than a second design.

  What the panel's second group holds is not lost with it: "Your posts" and
  "Watching" are the tabs across /home's own feed, and "Your listings" is a
  heading on /account, which the account menu in the bar still opens.
*/
.apptabs {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 60;
  align-items: stretch;
  height: calc(var(--tabs-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  border-top: 1px solid var(--line);
  background: var(--rail-bg);
}

.apptab {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: 0;
  font-size: var(--t-2xs);
  text-decoration: none;
  color: var(--ink-4);
}

.apptab svg {
  flex: none;
}

.apptab[aria-current='page'] {
  color: var(--accent);
  font-weight: 600;
}

/* -------------------------------------------------------------- the sweep --

   1180 is where the labels stop fitting beside a readable column, so the panel
   keeps its rows and drops its words. 860 is where it goes entirely and the
   strip takes over. Both numbers are the ones the approved workspace design
   came with; the only change here is that a fixed panel narrows without the
   grid it used to be a column of. */

@media (max-width: 1180px) {
  :root {
    --rail-w: 68px;
  }

  .rail {
    padding-left: var(--s-2);
    padding-right: var(--s-2);
    align-items: stretch;
  }

  /* The group heading really does go: "Workspace" and "Yours" are decoration
     beside icons, and each group's <nav> carries the same word as its label
     already. */
  .rail-label {
    display: none;
  }

  /*
    Everything else is HIDDEN, not removed. `display: none` takes a word out of
    the accessibility tree as well as the picture, and a panel of seven unnamed
    links is what that leaves between 861px and 1180px. These are the same six
    declarations as .shell-sr: off the screen, still read aloud, still the
    accessible name of the link they are inside.
  */
  .rail-brand-name,
  .rail-you-text,
  .rail-away span,
  .rail-item span {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .rail-brand,
  .rail-item,
  .rail-you,
  .rail-away {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
  }

  .rail-item[aria-current='page']::before {
    left: -8px;
  }
}

/*
  Your own places live in exactly one navigation at any one width.

  The panel carries them on a desktop. Below 860px the panel is gone and the
  strip along the bottom carries only the four Workspace places, so the account
  menu carries them instead — see APPBAR in scripts/build-header.mjs. Here they
  are switched off while the panel is on screen, so the two never both offer the
  same destination at the same time.
*/
.appmenu-narrow {
  display: none;
}

@media (max-width: 860px) {
  /* As heavy as the rule that turned it on. A media query adds no weight of
     its own, so a bare `.rail` here loses to the three selectors above and the
     panel stays on screen on a phone with the strip underneath it. */
  [data-session='in'] .in-app .rail {
    display: none;
  }

  .appmenu-narrow {
    display: flex;
  }

  [data-session='in'] .in-app .apptabs {
    display: flex;
  }

  [data-session='in'] .in-app {
    padding-left: 0;
    padding-bottom: calc(var(--tabs-h) + env(safe-area-inset-bottom, 0px));
  }

  .appbar {
    left: 0;
    padding: 0 var(--s-4);
  }

  .appbar-brand {
    display: flex;
  }
}

@media (max-width: 34rem) {
  .appsearch {
    display: none;
  }

  .appbar-right {
    margin-left: auto;
  }
}

/* Nobody prints a navigation panel. */
@media print {
  .rail,
  .appbar,
  .apptabs {
    display: none !important;
  }

  [data-session='in'] .in-app {
    padding: 0;
  }
}

/* -------------------------------------------------------------- the rest -- */

/* A label that is read aloud and never seen. Its own name, so it cannot
   collide with either page stylesheet's version of the same idea. */
.shell-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* One visible focus ring for the whole frame, in the accent, on every kind of
   control in it — link, button and the menu's summary alike. */
.rail a:focus-visible,
.appbar a:focus-visible,
.appbar button:focus-visible,
.appbar summary:focus-visible,
.appmenu-item:focus-visible,
.apptab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

@media (prefers-reduced-motion: reduce) {
  .rail *,
  .appbar *,
  .apptabs * {
    transition: none !important;
  }
}


/* ==========================================================================
   ONE FRAME, AND ONE PAGE TITLE, INSIDE THE PRODUCT
   ==========================================================================

   MEASURED at 1440px, signed in: /home put its H1 at x=260 and every other
   signed-in page put its H1 at x=350, because Home fills the area beside the
   rail while the rest centre a 64rem box inside it. Moving Home → Store → back
   slid the whole page 90px sideways under the reader. The reading width is
   right; the centring is what jumps, so inside the product the box hugs the
   rail instead of floating in the middle of what is left.

   The same walk found four different page-title sizes in one shell — 40px on
   the Store, 28px on the account, 27px on Publish, 22px on Home. A page title
   is one thing and gets one size. The marketing pages keep their bigger one:
   they are not in here. */

[data-session='in'] .in-app .wrap {
  margin-left: 0;
  margin-right: auto;
}

.in-app .page h1,
.in-app .form-page h1,
.in-app .acct h1,
.in-app .hello h1 {
  font-size: var(--t-2xl);
  line-height: 1.2;
  letter-spacing: -0.02em;
}


/* ==========================================================================
   THE CONVERSATION CARD — the same one on /home and on /community
   ==========================================================================

   These two pages drew the same row two different ways: /home in
   assets/app.css as a `.tcard`, /community in assets/site.css as a
   `.postcard`, with different words at the foot and a like button on one and
   not the other. Moving between them read as moving between two products.

   There is one card now, rendered by `cardHtml` in api/store/_home.js. Its
   rules live HERE because this is the one stylesheet both pages load: /home
   loads app.css + shell.css, and every store page loads site.css + shell.css.
   Neither loads the other's.

   `.chip` and the kind colours are scoped under `.tcard`, because site.css
   already means something else by `.chip` — the store's filter row — and a
   card must not reach out and restyle it.
   ========================================================================== */

/* A list of these cards is a flush stack, not a gapped grid: the cards carry
   their own separators. site.css's `.feed` is a grid for the postcards it used
   to hold, so the list says which it is. */
.feed-cards {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
  margin: 0;
  list-style: none;
}

.tcard .chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 22px; padding: 0 var(--s-2);
  border-radius: var(--r-sm); border: 1px solid var(--line);
  background: var(--surface-2);
  font-size: var(--t-2xs); font-weight: 500; color: var(--ink-2);
  letter-spacing: 0.01em; white-space: nowrap;
}

.tcard .k-skill        { color: #6b3fb5; border-color: rgba(107,63,181,0.28); background: rgba(107,63,181,0.08); }
.tcard .k-instructions { color: #6b3fb5; border-color: rgba(107,63,181,0.28); background: rgba(107,63,181,0.08); }
.tcard .k-routine      { color: #6b3fb5; border-color: rgba(107,63,181,0.28); background: rgba(107,63,181,0.08); }
.tcard .k-mcp          { color: #126b6b; border-color: rgba(18,107,107,0.28);  background: rgba(18,107,107,0.08); }
.tcard .k-extension    { color: #126b6b; border-color: rgba(18,107,107,0.28);  background: rgba(18,107,107,0.08); }
.tcard .k-tool         { color: #46691f; border-color: rgba(70,105,31,0.28);   background: rgba(70,105,31,0.08); }
.tcard .k-hooks        { color: #46691f; border-color: rgba(70,105,31,0.28);   background: rgba(70,105,31,0.08); }

.tcard-likes {
  font-size: var(--t-xs);
  color: var(--ink-4);
}

.badge-ok   { color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 40%, transparent);   background: color-mix(in srgb, var(--ok) 10%, transparent); }
.badge-need { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); background: color-mix(in srgb, var(--warn) 10%, transparent); }
.badge-new  { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }
:root[data-theme='dark'] .badge-new { color: var(--accent-hi); }
.byline { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; min-width: 0; }
.byline-name { font-size: var(--t-sm); font-weight: 600; color: var(--ink); letter-spacing: -0.01em; }
a.byline-name:hover { color: var(--accent); }
.byline-handle { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-4); }
.byline-when { display: inline-flex; align-items: center; gap: var(--s-2); white-space: nowrap; }
.byline-dot { color: var(--ink-4); font-size: var(--t-xs); }
.byline-time { font-size: var(--t-xs); color: var(--ink-4); }
.standing {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--t-2xs); color: var(--ink-3);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 1px var(--s-2); height: 20px; white-space: nowrap;
}
.standing svg { width: 12px; height: 12px; color: var(--ink-4); }
.standing.is-mod { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }
.standing.is-mod svg { color: var(--accent); }
:root[data-theme='dark'] .standing.is-mod,
:root[data-theme='dark'] .standing.is-mod svg { color: var(--accent-hi); }
.facepile { display: inline-flex; align-items: center; }
.facepile .avatar { margin-left: -6px; box-shadow: 0 0 0 2px var(--surface); }
.facepile .avatar:first-child { margin-left: 0; }
.topic-tag {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: var(--t-2xs); color: var(--ink-3);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 0 var(--s-2); height: 22px; white-space: nowrap;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.topic-tag svg { width: 12px; height: 12px; color: var(--ink-4); flex: none; }
a.topic-tag:hover { color: var(--ink); border-color: var(--line-strong); }
a.topic-tag:hover svg { color: var(--ink-2); }
.tcard {
  display: flex; gap: var(--s-3);
  padding: var(--s-5);
  border-top: 1px solid var(--line-soft);
  transition: background var(--dur) var(--ease);
  position: relative;
}
.tcard:first-child { border-top: 0; }
.tcard:hover { background: var(--surface-2); }
.tcard-face { flex: none; }
.tcard-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.tcard-head { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; min-width: 0; min-height: 32px; }
.tcard-where { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.tcard-title {
  font-size: var(--t-base); font-weight: 600; letter-spacing: -0.016em;
  line-height: 1.35;
}
.tcard-title a { display: inline-block; }
.tcard-title a:hover { color: var(--accent); }
.tcard-body {
  font-size: var(--t-sm); color: var(--ink-3); line-height: 1.55;
  max-width: 68ch;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tcard-foot { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; margin-top: var(--s-1); min-height: 32px; }
.tcard-answer {
  display: flex; gap: var(--s-3); align-items: flex-start;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  max-width: 68ch;
}
.tcard:hover .tcard-answer { background: var(--surface); }
.tcard-answer .who {
  display: block;
  font-size: var(--t-2xs); color: var(--ink-4); margin-bottom: 2px;
}
.tcard-answer .who b { color: var(--ink-3); font-weight: 600; }
.tcard-answer p {
  font-size: var(--t-sm); color: var(--ink-2); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tcard-answer .mark { color: var(--ok); display: inline-flex; vertical-align: -2px; margin-right: 3px; }
.tcard-answer .mark svg { width: 13px; height: 13px; }
.tcard-open {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--t-xs); color: var(--ink-4);
}
.tcard-open svg { width: 14px; height: 14px; flex: none; }
.tcard-open b { color: var(--accent); font-weight: 600; }
a.tcard-open:hover b, a.tcard-open:focus-visible b { text-decoration: underline; text-underline-offset: 2px; }
          
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .tcard .k-skill,
  :root:not([data-theme='light']) .tcard .k-instructions,
  :root:not([data-theme='light']) .tcard .k-routine { color: #c4a4f2; border-color: rgba(196,164,242,0.3); background: rgba(196,164,242,0.10); }
  :root:not([data-theme='light']) .tcard .k-mcp,
  :root:not([data-theme='light']) .tcard .k-extension { color: #62c8c8; border-color: rgba(98,200,200,0.3); background: rgba(98,200,200,0.10); }
  :root:not([data-theme='light']) .tcard .k-tool,
  :root:not([data-theme='light']) .tcard .k-hooks { color: #9fcb6e; border-color: rgba(159,203,110,0.3); background: rgba(159,203,110,0.10); }
}

/* These five belong to narrow screens and nowhere else — they were written
   inside a media query in assets/app.css and have to keep it. A tap target
   grown to 44px at every width turns a small topic tag into a button. */
@media (max-width: 860px) {
  .tcard-title a { padding-block: 12px; }
  a.topic-tag, .topic-tag { min-height: 44px; height: auto; padding-block: 0; }
  .tcard-open { min-height: 44px; }
  a.tcard-open { padding-inline: var(--s-2); margin-inline: calc(var(--s-2) * -1); }
  .byline-dot { display: none; }
}

/* The card's own face, scoped. site.css means a 64px profile picture by
   `.avatar`; app.css means a 28px face in a byline. Both are right on their own
   page, so the card names the one it needs. */
.tcard .avatar {
  width: 28px;
  height: 28px;
  flex: none;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0;
  border: 1px solid var(--line);
  overflow: hidden;
}

.tcard .avatar img { width: 100%; height: 100%; object-fit: cover; }
.tcard .avatar.md { width: 32px; height: 32px; font-size: var(--t-xs); }
.tcard .avatar.xs { width: 22px; height: 22px; font-size: 10px; }

/* site.css gives an h3 a top margin, which put a gap between the byline and the
   title that app.css never had. */
.tcard-title,
.tcard .tcard-title {
  margin: 0;
}

.tcard-body {
  margin: 0;
}
