/* =============================================================================
   FORC3 Grid — stylesheet

   Tokens are lifted from forc3mod-website/css/style.css on purpose: this app is
   meant to read as part of the same product, and the two sites sit one click
   apart. If the main site's palette moves, move these with it.

   Every var() here has a fallback. An undefined custom property does not make a
   declaration do nothing — for an inherited property it resolves to the
   parent's value, so a typo looks plausible rather than broken. That has
   shipped in this ecosystem before.
   ========================================================================== */

:root {
  --bg: #0a0b0e;
  --bg-alt: #0e1015;
  --surface: #14161d;
  --surface-2: #1a1d26;
  --border: #232634;
  --text: #eef0f4;
  --text-dim: #a6acbb;
  --text-mute: #6b7182;
  /* The footer's two lines. Not a new colour — --text-mute's exact hue and
     saturation (H 224.3, S 9.7%) with only the lightness moved, 46.5% -> 24%.
     Contrast against --bg goes 4.04:1 -> 1.73:1, which is far below any
     legibility threshold and is the point: this is boilerplate and an
     attribution, meant to be found when looked for and invisible otherwise.
     **Nothing but the footer may use this**, and anything that has to be read
     belongs on --text-mute or above. */
  --text-faint: #373a43;
  /* Orange, not the FORC3MOD blue. Chosen against the dark ground rather than
     picked by eye: --accent carries white button text at 4.53:1 (the blue it
     replaced managed 4.34), and --accent-2 reads at 9.02:1 on --bg (the blue
     managed 8.64). Both improve on what they replaced.
     NOTE: this deliberately diverges from forc3mod-website's palette, so the
     two no longer match. If they should, this is the block to revert. */
  --accent: #cf4a08;
  --accent-2: #ff9542;
  --accent-soft: rgba(255, 122, 40, 0.14);
  --ok: #3ddc84;
  --ok-soft: rgba(61, 220, 132, 0.12);
  --warn: #ffb020;
  --warn-soft: rgba(255, 176, 32, 0.12);
  --danger: #ff5c5c;
  --danger-soft: rgba(255, 92, 92, 0.12);
  /* Tightened twice now: 14/8, then 8/5, now 4/2. Nearly square, which is the
     look a tool wants. Everything that rounds reads these two — the only
     exceptions are three deliberate round things (the scrollbar thumb, the
     no-preview mark and the spinner) and they are meant to stay round. */
  --radius: 4px;
  --radius-sm: 2px;
  --shadow: 0 20px 60px rgba(0, 0, 0, .45);
  --font: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-head: 'Rajdhani', 'Inter', sans-serif;
  --container: 1200px;
  /* Height of a view's title row. It is the height of the controls that sit in
     it (a .btn is 8px padding + 1px border either side of a 15px/1.6 line), and
     it is pinned so the row does not shrink on views that have no controls. If
     .btn's metrics ever change, this changes with them. */
  --bar-height: 42px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* The window itself never scrolls — .scroll-area does. That is what keeps the
   scrollbar below the topbar instead of running the full height beside it. */
html, body { height: 100%; overflow: hidden; }

/* `hidden` is only a default `display: none`, so ANY display rule beats it —
   `.connect { display: flex }` kept the connect panel on screen after a
   successful connect, on both builds. Attribute checks do not catch this:
   el.hidden reads true while the element is plainly visible, so verify with
   getComputedStyle, not with the property. */
[hidden] { display: none !important; }

/* --- scrollbars -------------------------------------------------------------
   The default Windows bar is light grey with arrow buttons and reads as a
   foreign object on a dark page.

   ⚠ The -webkit- and standards rules are written SEPARATELY and never
   comma-grouped: Chromium drops an entire rule that contains a selector it does
   not recognise, so grouping them would silently lose the styling it does
   support. Same trap as the slider rules in FORC3 Designer. */

::-webkit-scrollbar { width: 14px; height: 14px; }

::-webkit-scrollbar-track { background: var(--bg-alt, #0e1015); }

::-webkit-scrollbar-thumb {
  background: #3a4055;
  border-radius: 999px;
  /* A border in the track's colour, not padding: it insets the thumb while
     keeping the full 14px as the grab target. */
  border: 2px solid var(--bg-alt, #0e1015);
}
::-webkit-scrollbar-thumb:hover { background: #4b5270; }
::-webkit-scrollbar-thumb:active { background: var(--accent, #cf4a08); }

/* The stepper arrows are dead weight on a dark UI and nothing else here has
   them. */
::-webkit-scrollbar-button { display: none; }
::-webkit-scrollbar-corner { background: var(--bg-alt, #0e1015); }

/* ⚠ Do NOT add `scrollbar-color` here. In Chromium, specifying it switches the
   element to the STANDARD scrollbar and the ::-webkit- pseudo-elements above
   are ignored entirely — thumb width, radius and hover all silently revert.
   It was set once and quietly undid every rule above it.

   The cost is that non-Chromium engines get their default bar, which is
   acceptable: the desktop build is WebView2, and the web build already requires
   Chromium for the File System Access API. */

/* Reserve the scrollbar's width whether or not it is showing. Without this,
   switching from a view that scrolls (My Garage) to one that does not (the
   Library) widens the content box by the scrollbar and every centred container
   jumps sideways. */
.scroll-area {
  flex: 1;
  min-height: 0;              /* a flex child will not shrink below content without it */
  overflow-y: auto;
  scrollbar-gutter: stable;
  display: flex;
  flex-direction: column;
}

body {
  background: var(--bg, #0a0b0e);
  color: var(--text, #eef0f4);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3, h4 {
  font-family: var(--font-head);
  font-weight: 700;
  letter-spacing: .01em;
  line-height: 1.15;
}
h1 { font-size: 2rem; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.1rem; }

a { color: var(--accent-2, #ff9542); text-decoration: none; }
a:hover { text-decoration: underline; }

code {
  font-family: ui-monospace, 'Cascadia Code', Consolas, monospace;
  font-size: .875em;
  background: var(--surface-2, #1a1d26);
  border: 1px solid var(--border, #232634);
  border-radius: 2px;
  padding: .1em .4em;
}
code.path { white-space: nowrap; }

.wrap { width: 100%; max-width: var(--container, 1200px); margin: 0 auto; padding: 0 24px; }
.muted { color: var(--text-mute, #6b7182); }
.ico { width: 1em; height: 1em; display: inline-block; vertical-align: -.15em; }

/* --- header ---------------------------------------------------------------- */

/* Outside the scroll container, so it does not move and needs no sticky. Its
   z-index still matters: the menu dropdowns hang out of it over the content. */
.topbar {
  flex: none;
  position: relative;
  z-index: 40;
  background: var(--bg, #0a0b0e);
}

.header {
  border-bottom: 1px solid var(--border, #232634);
}
.header__inner { display: flex; align-items: center; gap: 24px; height: 62px; }
.header__right { margin-left: auto; }

/* A stacked lockup: wordmark over tagline. The column's width is the widest
   child, which is the wordmark — so a stretched tagline is exactly as wide as
   the logo, whatever height the logo is set to. */
.brand { display: flex; flex-direction: column; align-items: stretch; gap: 4px; color: var(--text); }
.brand:hover { text-decoration: none; }

/* Tracked to the wordmark's width by FLEX, not by a letter-spacing value.
   `space-between` puts the first glyph on the left edge and the last on the
   right edge and shares what is left between them, so the tagline spans the
   logo exactly — and keeps spanning it when the logo is resized. A tuned
   letter-spacing gets one logo height right and is wrong at every other, which
   is what this replaces.

   It also removes the trailing-gap problem: letter-spacing adds its gap AFTER
   the final letter too, which pushed the block off the wordmark's left edge. */
.brand__tag {
  display: flex;
  justify-content: space-between;
  font-size: .5rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text-mute, #6b7182);
  white-space: nowrap;
}

/* The wordmark ships with no fills of its own, so each half takes a theme
   colour. Height is the single knob — width follows the viewBox. */
/* 19.665px = 23 * 0.9 * 0.95. The width follows the viewBox, and it is this
   width the tagline above spans — change this one number and the tagline
   re-tracks itself. align-self keeps the column's `stretch` from touching it. */
.brand__logo { height: 19.665px; width: auto; display: block; align-self: flex-start; }
/* Dimmer than body text on purpose, so GRID carries the emphasis. */
.brand__logo-forc3 { fill: var(--text-dim, #a6acbb); }
.brand__logo-grid  { fill: var(--accent-2, #ff9542); }

.nav { display: flex; gap: 4px; }
.nav__link {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  color: var(--text-dim, #a6acbb);
  padding: 7px 13px;
  border-radius: var(--radius-sm, 2px);
}
.nav__link:hover { color: var(--text, #eef0f4); background: var(--surface, #14161d); }
.nav__link.is-active { color: var(--text, #eef0f4); background: var(--accent-soft, rgba(255,122,40,.14)); }

/* --- menu bar ---------------------------------------------------------------
   Modelled on FORC3 Designer's File/Settings/Help bar, adapted to this app's
   tokens. Opens on click rather than hover: a menu that drops on hover fires
   while the pointer is only crossing the bar to reach something else. */

.menu-bar {
  background: var(--bg-alt, #0e1015);
  border-bottom: 1px solid var(--border, #232634);
}
/* Above the header, so its dropdowns must sit above it too. */
.menu-bar { position: relative; z-index: 2; }
.menu-bar__inner { display: flex; }

.menu-item { position: relative; }
.menu-item > button {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: .75rem;
  line-height: 1.4;
  color: var(--text-dim, #a6acbb);
  padding: 4px 11px;
}
.menu-item > button:hover,
.menu-item.open > button { background: var(--surface, #14161d); color: var(--text, #eef0f4); }

/* An action in the menu bar rather than a menu. Same type scale and padding as
   a .menu-item button so the row reads as one strip, but carrying the accent,
   because a thing that DOES something should not look identical to a thing that
   opens a list. */
.menu-action {
  appearance: none;
  display: inline-flex;
  align-items: center;
  /* Pushed to the far end of the bar, away from the two menus. It is the only
     thing here that starts something outside the app, so it does not belong in
     the run of dropdowns. Works because it is a direct child of the bar's flex
     container — nested one level deeper there would be no free space to push
     against and it would simply sit where it was. */
  margin-left: auto;
  gap: 6px;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: .75rem;
  line-height: 1.4;
  color: var(--accent-2, #ff9542);
  padding: 4px 11px;
}
.menu-action:hover:not(:disabled) {
  background: var(--accent-soft, rgba(255, 122, 40, .14));
  color: var(--accent-2, #ff9542);
}
/* Same treatment as .menu-dropdown button:disabled. Shown rather than hidden,
   so the web build says WHY through its title instead of quietly lacking a
   feature the desktop app has. */
.menu-action:disabled { opacity: .4; cursor: not-allowed; }
.menu-action__ico { width: 11px; height: 11px; flex: none; }

.menu-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 190px;
  z-index: 30;
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-radius: 0 0 var(--radius-sm, 2px) var(--radius-sm, 2px);
  box-shadow: var(--shadow);
  padding: 4px 0;
}
.menu-item.open .menu-dropdown { display: block; }

.menu-dropdown button {
  appearance: none;
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: .75rem;
  color: var(--text-dim, #a6acbb);
  padding: 6px 14px;
  white-space: nowrap;
}
.menu-dropdown button:hover:not(:disabled) { background: var(--surface-2, #1a1d26); color: var(--text, #eef0f4); }
.menu-dropdown button:disabled { opacity: .4; cursor: not-allowed; }
.menu-dropdown .menu-sep { height: 1px; background: var(--border, #232634); margin: 4px 0; }

/* --- buttons --------------------------------------------------------------- */

.btn {
  appearance: none;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 15px;
  border-radius: var(--radius-sm, 2px);
  border: 1px solid var(--border, #232634);
  background: var(--surface-2, #1a1d26);
  color: var(--text, #eef0f4);
  white-space: nowrap;
  transition: background .15s, border-color .15s, opacity .15s;
}
.btn:hover:not(:disabled) { background: #222634; border-color: #333849; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn--primary { background: var(--accent, #cf4a08); border-color: var(--accent, #cf4a08); color: #fff; }
.btn--primary:hover:not(:disabled) { background: #b03d06; border-color: #b03d06; }
.btn--ghost { background: transparent; }
.btn--quiet { background: transparent; border-color: transparent; color: var(--text-mute, #6b7182); }
.btn--quiet:hover:not(:disabled) { background: var(--surface, #14161d); color: var(--text-dim, #a6acbb); }
.btn--danger { background: var(--danger-soft, rgba(255,92,92,.12)); border-color: rgba(255,92,92,.35); color: #ff8080; }
.btn--danger:hover:not(:disabled) { background: rgba(255, 92, 92, .2); border-color: var(--danger, #ff5c5c); }
.btn--discord { background: #5865f2; border-color: #5865f2; color: #fff; }
/* An icon-only button sitting among text buttons. `align-self: stretch` makes
   it match their height without hardcoding one, so it stays right if the button
   padding or font ever changes. */
.btn--icon-only {
  align-self: stretch;
  padding: 0 13px;
}
.btn--icon-only .ico { width: 16px; height: 16px; }

.btn--icon { padding: 5px 10px; color: var(--text-mute, #6b7182); }
.btn--icon:hover:not(:disabled) { color: var(--text, #eef0f4); }
.btn--icon .ico { width: 15px; height: 15px; }
.btn--discord:hover:not(:disabled) { background: #4854e0; border-color: #4854e0; }
.btn--lg { padding: 12px 22px; font-size: 1rem; }
.btn--sm { padding: 5px 10px; font-size: .8125rem; }

/* --- layout ---------------------------------------------------------------- */

.main { flex: 1; padding: 32px 24px 64px; }
.view { display: none; }
.view.is-active { display: block; }

.bar {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 20px;
  /* Every view's title must sit on the same line. My Garage's row is tall
     because it carries a search box and four buttons; the Library and How it
     works rows carry only a heading, and without this they collapsed ~15px
     shorter, moving the title up and everything below it with it. */
  min-height: var(--bar-height, 42px);
}
.bar__left { display: flex; align-items: center; gap: 10px; }
.bar__right { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* inline-flex + line-height:1, not the inherited 1.6. A chip's text is
   UPPERCASE, so it has no descenders — under the body's line-height the
   descender space below the caps goes unused and the ink sits high. Measured
   before the fix: 8.00px of space above the ink against 10.67px below.
   line-height:1 makes the line box shorter than the font's content area, and
   the overflow splits evenly, which is what balances caps in a box. */
.chip {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  padding: 6px 9px;
  border-radius: 3px;
  background: var(--surface-2, #1a1d26);
  border: 1px solid var(--border, #232634);
  color: var(--text-dim, #a6acbb);
}
.chip--ok { background: var(--ok-soft, rgba(61,220,132,.12)); border-color: rgba(61,220,132,.3); color: var(--ok, #3ddc84); }
.chip--warn { background: var(--warn-soft, rgba(255,176,32,.12)); border-color: rgba(255,176,32,.3); color: var(--warn, #ffb020); }
.chip--danger { background: var(--danger-soft, rgba(255,92,92,.12)); border-color: rgba(255,92,92,.3); color: #ff8080; }

.search {
  font: inherit;
  color: var(--text, #eef0f4);
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  padding: 8px 12px;
  min-width: 0;
  width: 220px;
}
.search:focus { outline: none; border-color: var(--accent, #cf4a08); }

/* --- connect panel --------------------------------------------------------- */

.connect { display: flex; justify-content: center; padding: 48px 0; }
.connect__card {
  max-width: 680px;
  text-align: center;
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius, 4px);
  padding: 44px 40px;
  box-shadow: var(--shadow);
}
.lede { color: var(--text-dim, #a6acbb); font-size: 1.0625rem; margin: 14px 0 28px; text-align: left; }
.connect__actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.connect__hint { margin-top: 20px; font-size: .875rem; color: var(--text-mute, #6b7182); }
.connect__hint i { color: var(--text-dim, #a6acbb); font-style: italic; }

/* --- notices --------------------------------------------------------------- */

.notice {
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-left: 3px solid var(--accent, #cf4a08);
  border-radius: var(--radius-sm, 2px);
  padding: 16px 18px;
  color: var(--text-dim, #a6acbb);
  margin-top: 16px;
  text-align: left;
}
.notice strong { color: var(--text, #eef0f4); display: block; margin-bottom: 4px; }
.notice--warn { border-left-color: var(--warn, #ffb020); }
.notice--danger { border-left-color: var(--danger, #ff5c5c); }

/* --- livery grid ----------------------------------------------------------- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  /* Tighter than 16px so the width saved goes into the cards, and from there
     into the preview — the thumbnail is 16:9 off the card's width, so a wider
     card is a bigger picture. Type and buttons are sized in rem and do not
     follow. */
  gap: 10px;
}

.card {
  background: var(--surface, #14161d);
  /* Transparent, not absent: the 1px is still reserved, so colouring it on
     hover cannot shift the card or anything inside it. */
  border: 1px solid transparent;
  border-radius: var(--radius, 4px);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-width: 0;                 /* grid children need this to shrink below content */
  transition: border-color .15s, box-shadow .15s;
}
/* The highlight is a 1px ring OUTSIDE the existing border, not a thicker
   border: growing the border would shrink the content box and nudge everything
   inside the card by a pixel on every hover. box-shadow draws outside the
   layout entirely, so nothing moves. */
.card:hover {
  border-color: #3d4457;
  box-shadow: 0 0 0 1px #3d4457;
}

.card__thumb {
  /* Matches the 16:9 crop the backend now applies around the car (see
     gamethumbs::crop_to_car). A square here would letterbox the crop straight
     back into the empty space it was made to remove. */
  aspect-ratio: 16 / 9;
  background: var(--bg-alt, #0e1015);
  border-bottom: 1px solid var(--border, #232634);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* `cover`, not `contain`: the image is already cropped to the car, so any
   residual mismatch should trim a sliver rather than reintroduce bars. */
.card__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Shown when the game has never rendered this livery. Deliberately quiet: it is
   an ordinary state with a clear next step, not an error, so it should not
   shout the way a red badge would. */
.no-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 18px;
  text-align: center;
  width: 100%;
  height: 100%;
}
.no-preview__mark {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px dashed var(--border, #232634);
  position: relative;
  flex: none;
}
/* ::before, not ::after — the tooltip system owns ::after everywhere here. */
.no-preview__mark::before {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 16px;
  height: 1px;
  background: var(--text-mute, #6b7182);
}
.no-preview__text {
  font-size: .8125rem;
  line-height: 1.4;
  color: var(--text-mute, #6b7182);
  max-width: 20ch;
}
.card__thumb .spinner { position: absolute; }
.card__badges { position: absolute; top: 8px; left: 8px; display: flex; gap: 6px; flex-wrap: wrap; }

.card__body { padding: 13px 15px; flex: 1; min-width: 0; }
/* A button, so it is keyboard reachable — stripped back to look like text
   until hovered, where it earns an affordance for the rename it triggers. */
.card__title {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: text;
  text-align: left;
  display: block;
  width: 100%;
  color: var(--text, #eef0f4);
  font-family: var(--font-head);
  font-size: .95rem;
  font-weight: 700;
  margin-bottom: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card__title:hover { text-decoration: underline dotted var(--text-mute, #6b7182); }
.card__title:focus-visible { outline: 2px solid var(--accent, #cf4a08); outline-offset: 2px; }

.card__title-edit {
  font-family: var(--font-head);
  font-size: .95rem;
  font-weight: 700;
  width: 100%;
  margin-bottom: 3px;
  color: var(--text, #eef0f4);
  background: var(--bg-alt, #0e1015);
  border: 1px solid var(--accent, #cf4a08);
  border-radius: 2px;
  padding: 1px 5px;
  min-width: 0;
}
.card__title-edit:focus { outline: none; }
/* Flex so the favourite star sits centred on the text rather than riding the
   line box. text-transform is kept: it is what tidies a model-id fallback. */
.card__car {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-size: .8125rem;
  color: var(--text-dim, #a6acbb);
  text-transform: capitalize;
}

/* The accent, not gold: this is the app's own "picked" colour, the same one the
   active nav item and the suggested car in the picker already use. A gold star
   would be the only warm-yellow thing in the whole interface. */
.card__fav {
  flex: none;
  width: .95em;
  height: .95em;
  color: var(--accent-2, #ff9542);
}

/* Library cards carry a line the garage's do not: who made it, how big, how
   many people have taken it. Small and quiet — it is context, not the point. */
.card__meta {
  font-size: .75rem;
  color: var(--text-mute, #6b7182);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The paging button, centred under the grid rather than stuck to a card. */
.library__more { display: flex; justify-content: center; margin-top: 18px; }

.card__foot {
  display: flex;
  gap: 6px;
  padding: 11px 15px;
  background: var(--bg-alt, #0e1015);
}
/* The two named actions share the width; the icon button takes only what it
   needs, so it reads as secondary rather than as a third equal choice. */
.card__foot .btn { flex: 1; }
.card__foot .btn--icon { flex: 0 0 auto; }

.empty { text-align: center; padding: 64px 24px; color: var(--text-dim, #a6acbb); }
.empty p + p { margin-top: 6px; }

/* --- prose ----------------------------------------------------------------- */

.prose { max-width: 760px; color: var(--text-dim, #a6acbb); }
.prose h3 { color: var(--text, #eef0f4); margin: 28px 0 8px; }
.prose h3:first-child { margin-top: 0; }
.prose p { margin-bottom: 12px; }
.prose b { color: var(--text, #eef0f4); }

/* --- modal ----------------------------------------------------------------- */

.modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; padding: 24px; }
.modal[hidden] { display: none; }
.modal__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, .65); backdrop-filter: blur(3px); }
.modal__box {
  position: relative;
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius, 4px);
  box-shadow: var(--shadow);
  width: 100%;
  max-width: 540px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.modal__head { display: flex; align-items: center; padding: 18px 20px 12px; gap: 12px; }
.modal__head h3 { flex: 1; min-width: 0; }
.modal__x {
  appearance: none; background: none; border: 0; cursor: pointer;
  color: var(--text-mute, #6b7182); font-size: 26px; line-height: 1; padding: 0 4px;
}
.modal__x:hover { color: var(--text, #eef0f4); }
.modal__body { padding: 0 20px 8px; overflow-y: auto; min-height: 0; color: var(--text-dim, #a6acbb); }
.modal__foot { display: flex; gap: 8px; justify-content: flex-end; padding: 14px 20px 18px; flex-wrap: wrap; }
.modal__foot:empty { display: none; }
/* For a dialog that is a list to work through — the moderation queue — rather
   than a question to answer. */
.modal__box--wide { max-width: 760px; }

/* --- category filter --------------------------------------------------------
   Reuses .menu-item / .menu-dropdown so it opens and closes on exactly the same
   rules as the menu bar, rather than growing a second set. */

.filter-menu { display: inline-flex; }
.filter-menu__btn { gap: 7px; }
.filter-menu__caret {
  color: var(--text-dim, #a6acbb);
  font-size: 1.05em;
  line-height: 1;
}

/* Left-anchored, matching where the control now sits — beside the title rather
   than at the end of the toolbar. */
.menu-dropdown--right { border-radius: var(--radius-sm, 2px); }

.menu-dropdown button { display: flex; align-items: center; gap: 14px; }
.menu-dropdown button.is-current { color: var(--accent-2, #ff9542); }
.menu-dropdown__count { margin-left: auto; color: var(--text-mute, #6b7182); }

/* The Favourites row carries the same star its cards do, so the filter and the
   mark it filters on read as one idea. inline-flex because the row's own flex
   only lays out name-then-count; the star lives inside the name. */
.menu-dropdown__starred { display: inline-flex; align-items: center; gap: 7px; }

/* --- car picker -------------------------------------------------------------
   FORC3 Designer's car picker, adapted: a search box over collapsible category
   sections. Only cars in the garage are listed, so most categories are absent
   rather than empty. */

.picker__search { width: 100%; margin-bottom: 10px; }

.picker {
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  overflow: hidden;
  max-height: 46vh;
  overflow-y: auto;
}

/* --- the cleanup list --------------------------------------------------------
   A <label> per row so the whole strip toggles its checkbox, with the folder
   button opting out of that by stopping its own click. */

.cleanup__row {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 8px 12px;
  cursor: pointer;
}
.cleanup__row + .cleanup__row { border-top: 1px solid var(--border, #232634); }
.cleanup__row:hover { background: var(--surface-2, #1a1d26); }
.cleanup__row input[type="checkbox"],
.cleanup__row input[type="radio"] { flex: none; accent-color: var(--accent, #cf4a08); }

/* The report dialog reuses the cleanup rows, as radio buttons. */
.report__reasons { margin: 12px 0 16px; max-height: none; }

/* min-width:0 so a long folder name ellipsises instead of pushing the button
   off the end of the row. */
.cleanup__text { display: flex; flex-direction: column; flex: 1; min-width: 0; line-height: 1.35; }
.cleanup__name {
  font-size: .8125rem;
  color: var(--text, #eef0f4);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cleanup__sub { font-size: .75rem; color: var(--text-mute, #6b7182); }
.cleanup__open { flex: none; }

.picker__group + .picker__group { border-top: 1px solid var(--border, #232634); }

.picker__cat {
  appearance: none;
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  background: var(--bg-alt, #0e1015);
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-dim, #a6acbb);
  padding: 8px 12px;
  text-align: left;
}
.picker__cat:hover { background: var(--surface-2, #1a1d26); color: var(--text, #eef0f4); }

/* ::before, not ::after — the tooltip system owns ::after. Rotated rather than
   swapped for a second glyph so the two states cannot drift apart. */
.picker__caret::before {
  content: '▸';
  display: inline-block;
  transition: transform .12s;
  color: var(--text-mute, #6b7182);
}
.picker__group.is-open .picker__caret::before { transform: rotate(90deg); }

.picker__cat-name { flex: 1; min-width: 0; }
.picker__count { color: var(--text-mute, #6b7182); font-weight: 500; }

.picker__cars { display: none; }
.picker__group.is-open .picker__cars { display: block; }

.picker__car {
  appearance: none;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: .875rem;
  color: var(--text, #eef0f4);
  padding: 7px 12px 7px 30px;
  text-align: left;
}
.picker__car:hover { background: var(--surface-2, #1a1d26); }
.picker__car.is-current { color: var(--accent-2, #ff9542); }

.picker__tag {
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-mute, #6b7182);
}

.picker__none { padding: 14px 12px; color: var(--text-mute, #6b7182); font-size: .875rem; }

/* No category headers above it, so it does not need their indent. */
.picker__car--flush { padding-left: 12px; }
.picker__guid {
  font-family: ui-monospace, Consolas, monospace;
  font-size: .6875rem;
  color: var(--text-mute, #6b7182);
  margin-left: auto;
}

/* Car chooser inside the install modal */
.choice { display: block; width: 100%; text-align: left; margin-bottom: 8px; }
.choice__guid { font-family: ui-monospace, Consolas, monospace; font-size: .75rem; color: var(--text-mute, #6b7182); }

/* A source choice explains itself, so it wraps and sits taller than a plain
   button. Normal weight and normal case: the label above it is the choice, the
   hint is what it means. */
.choice__hint {
  font-size: .8125rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.45;
  margin-top: 4px;
  white-space: normal;
  color: var(--text-dim, #a6acbb);
}
.btn--primary .choice__hint { color: rgba(255, 255, 255, .82); }

/* The base colour texture, shown so an import can be recognised before it is
   installed. It is a UV sheet rather than a render — wide, mostly flat, and
   worth showing small. */
.import__preview {
  display: block;
  width: 100%;
  max-width: 220px;
  margin: 0 auto 12px;
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  background: var(--bg-alt, #0e1015);
}

.field { margin-bottom: 14px; }
.field label { display: block; font-size: .8125rem; color: var(--text-dim, #a6acbb); margin-bottom: 5px; }
.field input, .field select {
  font: inherit; width: 100%;
  color: var(--text, #eef0f4);
  background: var(--bg-alt, #0e1015);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  padding: 9px 12px;
  min-width: 0;                 /* stops a native select pushing its container wide */
}
.field input:focus, .field select:focus { outline: none; border-color: var(--accent, #cf4a08); }
.field textarea {
  font: inherit;
  width: 100%;
  resize: vertical;
  color: var(--text, #eef0f4);
  background: var(--bg-alt, #0e1015);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  padding: 9px 12px;
}
.field textarea:focus { outline: none; border-color: var(--accent, #cf4a08); }

/* --- moderation queue --------------------------------------------------------
   One block per pack: what it is, who reported it and why, and the actions. A
   confirmation replaces the action row in place rather than opening a second
   dialog over the queue. */

.mod__item {
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  background: var(--bg-alt, #0e1015);
  padding: 12px;
  margin-bottom: 12px;
}
.mod__head { display: flex; gap: 12px; align-items: flex-start; }
.mod__head .chip { flex: none; }
.mod__thumb {
  position: relative;
  flex: none;
  width: 128px;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
}
.mod__thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.mod__thumb .no-preview__text { font-size: .6875rem; }
.mod__info { flex: 1; min-width: 0; line-height: 1.4; }
.mod__name {
  color: var(--text, #eef0f4);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mod__sub { font-size: .75rem; color: var(--text-mute, #6b7182); }
.mod__reports { list-style: none; margin: 10px 0 0; padding: 0; font-size: .8125rem; }
.mod__report {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--border, #232634);
}
.mod__report b { color: var(--text, #eef0f4); font-weight: 600; }
.mod__report-text { flex: 1; min-width: 0; }
.mod__note { color: var(--text-dim, #a6acbb); font-style: italic; overflow-wrap: anywhere; }
.mod__actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.mod__confirm { margin-top: 10px; }
.mod__explain { font-size: .8125rem; margin-bottom: 8px; }
.mod__noteinput {
  font: inherit;
  font-size: .8125rem;
  width: 100%;
  color: var(--text, #eef0f4);
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  padding: 7px 10px;
}
.mod__noteinput:focus { outline: none; border-color: var(--accent, #cf4a08); }
.mod__textures { margin-top: 10px; }
/* A real base colour is a 4096² square; at the dialog's full width that is a
   700px-tall block to scroll past. Capped, and shown whole. */
.mod__textures img {
  display: block;
  width: 100%;
  max-height: 55vh;
  object-fit: contain;
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  background: var(--surface, #14161d);
  margin-bottom: 4px;
}
.mod__empty { padding: 18px 2px; color: var(--text-mute, #6b7182); }
.mod__urls {
  margin-top: 8px;
  font-family: ui-monospace, Consolas, monospace;
  font-size: .75rem;
  color: var(--text, #eef0f4);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: all;
}

.kv { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; font-size: .875rem; margin: 12px 0; }
.kv dt { color: var(--text-mute, #6b7182); }
.kv dd { color: var(--text, #eef0f4); min-width: 0; overflow-wrap: anywhere; }

/* --- card context menu ------------------------------------------------------
   Fixed, not absolute: it is positioned from viewport coordinates and must not
   move with the scroll container it was opened over. */

.ctx {
  position: fixed;
  outline: none;                 /* it takes focus so Escape works; do not ring it */
  z-index: 120;
  min-width: 214px;
  padding: 5px 0;
  background: var(--surface, #14161d);
  border: 1px solid var(--border, #232634);
  border-radius: var(--radius-sm, 2px);
  box-shadow: var(--shadow);
}

.ctx__item {
  appearance: none;
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: .8125rem;
  color: var(--text-dim, #a6acbb);
  padding: 7px 14px;
  white-space: nowrap;
}
.ctx__item:hover:not(:disabled) { background: var(--surface-2, #1a1d26); color: var(--text, #eef0f4); }
.ctx__item:focus-visible { outline: 2px solid var(--accent, #cf4a08); outline-offset: -2px; }
.ctx__item:disabled { opacity: .4; cursor: not-allowed; }

/* Red only here, where the choice is deliberate — not on every card. */
.ctx__item--danger { color: #ff8080; }
.ctx__item--danger:hover:not(:disabled) { background: var(--danger-soft, rgba(255,92,92,.12)); color: #ff9d9d; }

.ctx__sep { height: 1px; background: var(--border, #232634); margin: 5px 0; }

/* --- toasts ---------------------------------------------------------------- */

.toasts { position: fixed; right: 20px; bottom: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; align-items: flex-end; }
.toast {
  background: var(--surface-2, #1a1d26);
  border: 1px solid var(--border, #232634);
  border-left: 3px solid var(--accent, #cf4a08);
  border-radius: var(--radius-sm, 2px);
  padding: 11px 16px;
  box-shadow: var(--shadow);
  max-width: 380px;
  font-size: .875rem;
  animation: toast-in .18s ease-out;
}
.toast--ok { border-left-color: var(--ok, #3ddc84); }
.toast--warn { border-left-color: var(--warn, #ffb020); }
.toast--err { border-left-color: var(--danger, #ff5c5c); }
.toast b { display: block; color: var(--text, #eef0f4); }

@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* --- spinner --------------------------------------------------------------- */

.spinner {
  width: 22px; height: 22px;
  border: 2px solid var(--border, #232634);
  border-top-color: var(--accent, #cf4a08);
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.busy { display: flex; align-items: center; gap: 10px; color: var(--text-dim, #a6acbb); padding: 8px 0; }

/* --- drag & drop ----------------------------------------------------------- */

body.is-dragging::after {
  content: 'Drop a .fmgrid pack to install it';
  position: fixed;
  inset: 16px;
  z-index: 300;
  border: 2px dashed var(--accent, #cf4a08);
  border-radius: var(--radius, 4px);
  background: rgba(255, 122, 40, .07);
  display: flex;
  align-items: center;
  justify-content: center;
  /* A text-bearing pseudo-element must state its own typography — it inherits
     from <body>, which is styled for prose, not for a 1.4rem centred label. */
  font-family: var(--font-head);
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: none;
  color: var(--accent-2, #ff9542);
  pointer-events: none;
}

/* --- footer ---------------------------------------------------------------- */

.footer { border-top: 1px solid var(--border, #232634); padding: 20px 0; margin-top: auto; }
.footer__inner { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; font-size: .8125rem; color: var(--text-faint, #373a43); }
/* Beats the global `.muted`, which is a step brighter — both footer lines
   belong at the same level as each other, not at two. */
.footer .muted { color: var(--text-faint, #373a43); }

/* Still the accent, only quieter: --accent-2's exact hue (H 26.3), with
   saturation 100% -> 56% and lightness 62.9% -> 30%. 9.02:1 against --bg
   becomes 2.54:1, so it recedes with the rest of the footer while staying
   unmistakably the same orange — a link that shifts HUE to dim down stops
   reading as a link at all, which is what a first attempt at this did. It sits
   a step above the text beside it so it is still findable, and takes full
   strength on hover, which is where it earns the attention. */
.footer .linkish { color: #774722; }
.footer .linkish:hover { color: var(--accent-2, #ff9542); }

/* A button that reads as a link. Needed wherever a link would navigate the
   app's own window away from the app — which in the desktop build means every
   external link, since there is nowhere for it to go back to. Inherits its
   size and weight so it sits inside a sentence without disturbing it. */
.linkish {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  color: var(--accent-2, #ff9542);
}
.linkish:hover { text-decoration: underline; }
.linkish:focus-visible { outline: 2px solid var(--accent, #cf4a08); outline-offset: 2px; }

/* --- responsive ------------------------------------------------------------ */

@media (max-width: 720px) {
  .nav { order: 3; width: 100%; justify-content: center; }
  .header__inner { height: auto; padding: 10px 0; flex-wrap: wrap; }
  .bar__right { width: 100%; }
  .search { width: 100%; }
  .connect__card { padding: 28px 20px; }
  h1 { font-size: 1.6rem; }
}
