.fh-ingestion { width: min(100% - 2rem, 52rem); margin: .75rem auto; padding: .85rem 1rem; border: 1px solid #b8ada1; border-radius: 12px; background: var(--surface-primary, #fffdf9); color: var(--text-primary, #252321); max-height: 38vh; overflow: auto; font-size: 13px; }
.fh-ingestion-heading, .fh-ingestion-actions { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.fh-ingestion-heading strong { font-size: 16px; }
/* Borders are a **light grey**. The old `#9c8a79` was a dark brown, so a file
   row carrying two buttons showed its borders before its filename - decoration
   standing in front of the thing to read.

   The grey is not hand-picked; it is LibreChat's `--border-medium`. That token
   changes in dark mode (`#00000026` / `#d9d9e326`), and the attachment chips on
   the same screen already use it - a grey of our own would catch the eye for
   being different.

   Being pressable is said by one step darker on hover. The border stays because
   these buttons (delete, download original) are ones a misclick would cost
   something, and they need an edge. */
.fh-ingestion button {
  border: 1px solid var(--border-medium, rgba(127, 127, 127, 0.15));
  border-radius: 6px; padding: .25rem .6rem; cursor: pointer;
  background: transparent; color: inherit;
}
.fh-ingestion button:hover {
  border-color: var(--border-heavy, rgba(127, 127, 127, 0.3));
  background: var(--surface-hover, rgba(127, 127, 127, 0.08));
}
.fh-ingestion button:disabled { opacity: .5; cursor: wait; }
.fh-ingestion-help { margin: .5rem 0; color: var(--text-secondary, #625c55); }
.fh-ingestion-notice { padding: .45rem; background: #c38d3220; border-radius: 4px; }
.fh-ingestion-batch, .fh-ingestion-question { margin-top: .6rem; padding-top: .6rem; border-top: 1px solid #b8ada170; }
.fh-ingestion-file { display: flex; align-items: center; flex-wrap: wrap; gap: .35rem .65rem; padding: .45rem 0; }
.fh-ingestion-file strong { overflow-wrap: anywhere; }
.fh-ingestion-file span { color: var(--text-secondary, #625c55); }
.fh-ingestion-question p { margin: .4rem 0; white-space: pre-wrap; }
.fh-ingestion-question button { margin-left: .5rem; }

.fh-ingestion-viewer { width: min(90vw, 64rem); max-height: 85vh; overflow: auto; border: 1px solid #9c8a79; border-radius: 12px; padding: 1.2rem; background: var(--surface-primary, #fffdf9); color: var(--text-primary, #252321); }
.fh-ingestion-viewer::backdrop { background: #0007; }
.fh-ingestion-viewer button { padding: .4rem .7rem; margin: .3rem;
  border: 1px solid var(--border-medium, rgba(127, 127, 127, 0.15));
  border-radius: 6px; background: transparent; color: inherit; cursor: pointer; }
.fh-ingestion-viewer button:hover { border-color: var(--border-heavy, rgba(127, 127, 127, 0.3)); }
.fh-ingestion-unit-list { display: flex; flex-wrap: wrap; max-height: 22vh; overflow: auto; }
/* An author `display` beats the UA's `[hidden] { display: none }`, so the rule
   above kept the unit list on screen after the citation view set hidden on it
   (observed on 3097, 2026-09-14). Anything this file gives a display to has to
   say what hidden means for it. */
.fh-ingestion-viewer [hidden] { display: none; }
.fh-ingestion-viewer pre { white-space: pre-wrap; overflow-wrap: anywhere; border-top: 1px solid #b8ada170; padding-top: 1rem; tab-size: 4; }

/* In a chat the materials ride in the conversation, not above the composer.
   With a file attached the send is ours: LibreChat writes no user message
   until the answer is delivered, so a box by the input was the only thing on
   screen for the whole wait, in the one place the reader was not looking.

   Right-aligned, because this is the reader's own side of the conversation -
   it stands where their message will stand, and answer-delivery derives that
   message's id from the question so the block can pin itself above the real
   row once it arrives. */
.fh-ingestion-inline {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.375rem;
  width: 100%;
  max-width: 47rem;
  margin: 0.5rem auto;
  padding: 0;
  border: 0;
  background: none;
}

/* The one line left when the block is folded. It sits above the composer, so
   it takes the conversation's width, and it looks like a button without asking
   to be pressed - on most questions there is no reason to open it, but the
   count of attached files still has to be readable. */
.fh-ingestion-inline .fh-ingestion-summary {
  align-self: flex-end;
  border: 0;
  padding: 0.15rem 0.35rem;
  font-size: 12px;
  color: var(--text-secondary, #625c55);
}
.fh-ingestion-inline .fh-ingestion-summary::after {
  content: " ▾";
}
.fh-ingestion-inline.fh-ingestion-collapsed .fh-ingestion-summary::after {
  content: " ▸";
}
/* Folded leaves only the summary line. The list is hidden rather than removed
   so opening does not have to redraw it, and so a screen reader sees the same
   thing aria-expanded claims. */
.fh-ingestion-inline.fh-ingestion-collapsed > *:not(.fh-ingestion-summary) {
  display: none;
}

.fh-ingestion-inline .fh-ingestion-batch,
.fh-ingestion-inline .fh-ingestion-file,
.fh-ingestion-inline .fh-ingestion-question {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  text-align: right;
}

/* The chip is the whole attachment: an icon and a name, the way one reads
   anywhere else. Everything the panel showed beside it - byte counts, the
   retained-original flag, the unit tally - answered a question nobody was
   asking while they waited. */
.fh-ingestion-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  max-width: 22rem;
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--border-medium, rgba(127, 127, 127, 0.3));
  border-radius: 0.75rem;
  background: var(--surface-secondary, rgba(127, 127, 127, 0.08));
  font-size: 0.8125rem;
}

.fh-ingestion-icon {
  width: 1rem;
  height: 1rem;
  flex: none;
  opacity: 0.75;
}

.fh-ingestion-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* One line under the chip, and it is the line that changes: a 20,737-unit
   document took an hour on 2026-09-13, and without this a reader cannot tell
   a slow file from a stopped one. */
.fh-ingestion-inline .fh-ingestion-stage {
  padding-right: 0.2rem;
  font-size: 0.75rem;
  color: var(--text-secondary, #6b6b6b);
}

/* A citation opened from the answer shows one line: where, then the words.
   The unit list stays in the DOM because the citation handler clicks its
   button to make the selection - it is hidden, not removed. */
.fh-ingestion-viewer-single pre {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

.fh-ingestion-cite-mark {
  margin-right: 0.1rem;
  font-weight: 600;
  color: var(--text-secondary, #6b6b6b);
}

/* The checkbox choosing whether a document is used in the answer. It sits
   inside the chip, so it matches the icon's size. A file that cannot be read
   yet is disabled, and that has to be visible - pressing it would mean
   nothing. */
.fh-ingestion-chip input[type="checkbox"] {
  margin: 0 0.15rem 0 0;
  accent-color: #8b6e51;
  cursor: pointer;
}
.fh-ingestion-chip input[type="checkbox"]:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

/* Citation markers inside the answer. They must not break the flow of the
   prose, so they do not look like buttons; an underline is all that says they
   can be pressed - [F12] is already a mark that stands out. */
.fh-cite {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 0.15em;
}
.fh-cite:hover { text-decoration: underline; }

/* The right end of the block's heading row. One `margin-left:auto` does the
   pushing so the button stays at the end however long the heading grows - a
   project name will be added to it one day. */
.fh-ingestion-add { margin-left: auto; }

/* Buttons on a file row **line up at the right edge.**

   Sitting straight after the text puts them at a different place on every row,
   following the filename's length, and three rows read as a staircase (reported
   2026-09-15). `margin-left:auto` on the first button pushes the rest along with
   it - the rule holds however many buttons a row carries (reprocess as CP949,
   pause transfer).

   The chat side stacks vertically and is already right-aligned, so it is left
   alone. */
.fh-ingestion:not(.fh-ingestion-inline) .fh-ingestion-file button:first-of-type {
  margin-left: auto;
}

/* ============================================================================
   EXPERIMENT (experiment/claude-upload-ui): Claude-style attachment cards in
   chat. Each attachment becomes a rounded card — an icon tile on the left, the
   filename over a quiet status line on the right. Scoped to inline/chat mode;
   the chip wrapper is dissolved (display:contents) so its parts join one grid.
   ============================================================================ */
/* Square file tiles flowing left-to-right and wrapping. ONE scroll container:
   the inline block itself wraps the tiles, batches are dissolved (display:contents)
   so multiple upload batches don't each get their own scrollbar, and the summary
   and question rows take their own full-width line. Caps at the summary line plus
   two tile rows, then scrolls. */
.fh-ingestion-inline {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  align-content: flex-start;
  justify-content: flex-start;
  gap: 8px;
  max-height: 288px;
  overflow-y: auto;
  text-align: left;
}
.fh-ingestion-inline > .fh-ingestion-summary { flex: 0 0 100%; }
.fh-ingestion-inline .fh-ingestion-batch { display: contents; }
.fh-ingestion-inline .fh-ingestion-question { flex: 0 0 100%; }
/* Status/notice paragraphs ("자료 상태 확인 중…", error notices) get their own
   full-width line instead of sitting inside the tile grid. */
.fh-ingestion-inline > p { flex: 0 0 100%; margin: 0; }
.fh-ingestion-inline .fh-ingestion-file {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 118px;
  height: 118px;
  flex: none;
  box-sizing: border-box;
  padding: 10px 10px 22px;
  border: 1px solid var(--border-medium, rgba(127, 127, 127, 0.25));
  border-radius: 0.75rem;
  background: var(--surface-primary, #fffdf9);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  text-align: center;
  overflow: hidden;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.fh-ingestion-inline .fh-ingestion-file:hover {
  border-color: var(--border-heavy, rgba(127, 127, 127, 0.42));
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.fh-ingestion-inline .fh-ingestion-chip { display: contents; }
.fh-ingestion-inline .fh-ingestion-icon {
  width: 2rem; height: 2rem; padding: 0.45rem; box-sizing: border-box;
  border-radius: 0.5rem; flex: none;
  background: var(--surface-secondary, rgba(127, 127, 127, 0.1));
  color: var(--text-secondary, #625c55); opacity: 1;
}
.fh-ingestion-inline .fh-ingestion-name {
  width: 100%; flex: none;
  font-weight: 500; font-size: 0.78rem; line-height: 1.2;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; white-space: normal; word-break: break-word;
}
/* One-word status, pinned bottom-center of the tile; never wraps or overflows. */
.fh-ingestion-inline .fh-ingestion-stage {
  position: absolute; left: 6px; right: 6px; bottom: 7px;
  text-align: center; font-size: 0.7rem; font-weight: 600; line-height: 1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0;
  color: var(--text-secondary, #8b8375);
}
.fh-ingestion-inline .fh-ingestion-file button {
  margin-top: 3px; font-size: 0.66rem; padding: 2px 6px;
}
/* Ready: green "완료". */
.fh-ingestion-inline .fh-tile-ready .fh-ingestion-stage { color: #1f7a4d; }
/* Failed: red border + red "실패". */
.fh-ingestion-inline .fh-ingestion-file.fh-tile-failed { border-color: #b23127; }
.fh-ingestion-inline .fh-tile-failed .fh-ingestion-stage { color: #b23127; }
/* One icon in the top-right of a failed tile (keeps "실패" at the bottom):
   ↻ where a retry can change the answer, ✕ where it cannot and the only
   thing left to do is clear the row. The two never appear together.
   Selector carries an extra class so it beats the generic tile-button rule. */
.fh-ingestion-inline .fh-ingestion-file button.fh-tile-retry,
.fh-ingestion-inline .fh-ingestion-file button.fh-tile-delete {
  position: absolute; top: 5px; right: 5px; margin: 0;
  width: 20px; height: 20px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1;
  border: 1px solid #b23127; color: #b23127;
  background: var(--surface-primary, #fffdf9);
  border-radius: 50%; cursor: pointer;
}
.fh-ingestion-inline .fh-ingestion-file button.fh-tile-retry:hover,
.fh-ingestion-inline .fh-ingestion-file button.fh-tile-delete:hover {
  background: rgba(178, 49, 39, 0.08);
}
/* Small green spinner, top-left, while a tile is still processing. */
.fh-tile-spin {
  position: absolute; top: 7px; left: 7px;
  width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid rgba(94, 126, 110, 0.25);
  border-top-color: #5e7e6e;
  animation: fh-tile-spin 0.7s linear infinite;
}
@keyframes fh-tile-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .fh-tile-spin { animation-duration: 2s; } }

/* ===== duplicate-warning modal (grafted from the S7 branch) ===== */
.fh-s7-window { width: min(92vw, 560px); max-height: 85vh; overflow: auto; border: 1px solid var(--border-medium, #cdc4b2); border-radius: 14px; padding: 0; background: var(--surface-primary, #fffdf9); color: var(--text-primary, #252321); box-shadow: 0 14px 44px rgba(0, 0, 0, 0.28); }
.fh-s7-window::backdrop { background: rgba(20, 17, 13, 0.45); }
.fh-s7-window .fh-ingestion { border: 0; border-radius: 0; background: transparent; padding: 18px 22px; max-height: none; width: auto; margin: 0; }
.fh-s7-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 4px; }
.fh-s7-title strong { font-size: 15px; }
.fh-s7-help { font-size: 12.5px; color: var(--text-secondary, #625c55); margin: 0.4rem 0; }
.fh-s7-dupes .fh-s7-help { margin-bottom: 1.15rem; }
.fh-s7-add { border: 1px solid #5e7e6e; color: #fff; background: #5e7e6e; border-radius: 8px; padding: 6px 13px; font: inherit; font-weight: 600; font-size: 13px; cursor: pointer; flex: none; }
.fh-dup-list { display: flex; flex-direction: column; gap: 6px; max-height: 46vh; overflow: auto; margin: 0.5rem 0 1rem; }
.fh-dup-row { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border: 1px solid #e8e2d6; border-radius: 10px; background: var(--surface-primary, #fffdf9); cursor: pointer; transition: border-color 0.12s ease, box-shadow 0.12s ease; }
.fh-dup-row:hover { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06); }
.fh-dup-cb { flex: none; width: 16px; height: 16px; accent-color: #5e7e6e; cursor: pointer; }
.fh-dup-name { flex: 1; overflow-wrap: anywhere; }
.fh-dup-tag { flex: none; font-size: 12px; color: var(--text-secondary, #9a9182); }
.fh-dup-row:has(.fh-dup-cb:checked) { border-color: #5e7e6e; background: #e0ebe4; }
.fh-dup-row:has(.fh-dup-cb:checked) .fh-dup-tag { color: #5e7e6e; font-weight: 600; }
.fh-dup-foot { display: flex; justify-content: flex-end; }
.fh-dup-ok { min-width: 96px; }
