/*
 * What is left of the app-wide stylesheet once MudBlazor arrived (ADR-003).
 *
 * Everything that styles a control now comes from the theme in Theme/FlurfunkProTheme.cs
 * and from component-scoped .razor.css files. Three things cannot live there and stay
 * here: the loading indicator, which is painted before Blazor has started; the error
 * bar, which the framework shows by id; and the benchmark page, which deliberately runs
 * without MudBlazor so that the numbers in Planning/16 keep measuring the same thing.
 *
 * And the design tokens below, which are none of those three: they are here because a
 * component-scoped .razor.css file cannot read C#, and there are 21 of them.
 */

/* --- Design tokens ---------------------------------------------------------
 *
 * The palette of the CI handbook, the same one Theme/FlurfunkProTheme.cs builds the
 * MudBlazor theme from. That file is where a colour is decided and where the rule that
 * governs it is written down - which pairings are forbidden, what the 60-30-5
 * distribution means, why amber is never type. Read it before picking one of these.
 *
 * The brand colours are literal, because Petrol is Petrol in both schemes and a second
 * copy of a constant that never changes is harmless. The roles that do change are read
 * out of MudBlazor's own variables instead of being written twice: MudBlazor knows which
 * scheme the user actually chose - light, dark, or whatever the system says this
 * minute - and this file only ever knows what the system says. The literal after the
 * comma is not a duplicate of that value, it is the one used while no MudBlazor palette
 * exists yet, which is the second the loading indicator is painted in.
 */
:root {
    --ffp-petrol: #14505C;
    --ffp-petrol-tief: #0D3A44;
    --ffp-petrol-hell: #3C7885;
    --ffp-petrol-schrift: #003842;
    --ffp-blaugrau: #6B8C94;
    --ffp-blaugrau-hell: #8AAFB8;
    --ffp-dunst: #B8D3D9;
    --ffp-amber: #E0A458;
    --ffp-amber-tief: #B87F35;
    /* Amber as type on a light surface. Amber Tief is 3.43:1 on white and therefore
       cannot carry words at a normal size, whatever the handbook claims. */
    --ffp-amber-schrift: #95672B;
    --ffp-tinte: #1D2A2E;
    --ffp-grau: #6B7679;
    /* The secondary ink the app actually uses - Grau survives white and not Nebel, and
       one variable cannot know which of the two it has landed on. */
    --ffp-grau-tief: #677275;
    --ffp-nebel: #F2F6F7;
    --ffp-linie: #DCE5E7;
    --ffp-weiss: #FFFFFF;
    --ffp-erfolg: #2E7D5B;
    /* The status warning is Amber Tief: it is the only amber that survives on white. */
    --ffp-warnung: var(--ffp-amber-tief);
    --ffp-fehler: #A63A2E;

    --ffp-bg: var(--mud-palette-background, #FFFFFF);
    --ffp-surface: var(--mud-palette-surface, #F2F6F7);
    --ffp-fg: var(--mud-palette-text-primary, #1D2A2E);
    --ffp-muted: var(--mud-palette-text-secondary, #677275);
    --ffp-border: var(--mud-palette-divider, #DCE5E7);
    --ffp-accent: var(--mud-palette-primary, #14505C);

    /* The accent that writes words rather than filling a shape, and the one token here
       whose two values are genuinely different colours rather than the same colour at
       two lightnesses: Amber Schrift is 4.94:1 on white and 3.49:1 on the dark canvas,
       Amber is 2.2:1 on white and 7.89:1 on the dark canvas. Neither survives both, so
       the flip below is not a refinement, it is the only way either scheme passes.

       This one cannot be read out of a MudBlazor variable the way --ffp-muted is: no
       palette role holds an amber that is legible in both schemes, and MudBlazor marks
       the dark scheme with nothing in the DOM - it only swaps the variables. The shell
       therefore states the scheme itself, in MainLayout.razor, and the rule below reads
       it. Components use var(--ffp-unread) and never either amber directly. */
    --ffp-unread: var(--ffp-amber-schrift);
    /* The rail is Petrol Tief in both schemes, so this one does not flip either. */
    --ffp-sidebar-bg: var(--ffp-petrol-tief);
    --ffp-sidebar-fg: var(--ffp-dunst);

    /* 4px on controls, 8px on containers, and nothing rounder: pills are reserved for
       badges and status pips. */
    --ffp-radius: 4px;
    --ffp-radius-container: 8px;
    --ffp-sidebar-width: 275px;
    --ffp-context-width: 340px;
    /* Kept for anything that needs to line up with the bar. It is not what sets the
       bar's height - MudBlazor does that, and it offsets the clipped drawer and the main
       content from the same value, so a second number here would only be a second number
       to keep in step. Measured against the running bar rather than guessed: it is 56,
       which is also what this has said since 2.5a - it was the dense override that made
       it untrue, not the token. */
    --ffp-appbar-height: 56px;

    /* The height of every head that starts right under the app bar: the rail's workspace
       head, a conversation's head and the details drawer's head beside it. Their bottom
       hairlines are one line across the window, which they are only while all three take
       their height from here rather than from their own padding and type (#890). In rem,
       so a larger default font size grows all three together. */
    --ffp-pane-head-height: 4rem;

    /* The 4/8 rhythm every gap in the feed is a multiple of. */
    --ffp-space-xs: 4px;
    --ffp-space-sm: 8px;
    --ffp-space-md: 16px;
    --ffp-space-lg: 24px;
    --ffp-space-xl: 32px;

    /* The only shadow in the system, and only for what floats: menus, popovers, dialogs.
       Mixed from Tinte rather than black, so it reads as ink rather than as dusk. */
    --ffp-shadow-overlay: 0 1px 3px rgba(29, 42, 46, 0.08), 0 4px 12px rgba(29, 42, 46, 0.05);
}

/* Dark. Only the pre-boot values change - after the first render the lines above already
   follow the theme the user picked, and the system preference is the only thing that can
   be known before then. The values match PaletteDark in Theme/FlurfunkProTheme.cs. */
@media (prefers-color-scheme: dark) {
    :root {
        --ffp-bg: var(--mud-palette-background, #101D21);
        --ffp-surface: var(--mud-palette-surface, #16262B);
        --ffp-fg: var(--mud-palette-text-primary, #E5F3F8);
        --ffp-muted: var(--mud-palette-text-secondary, #B8D3D9);
        --ffp-border: var(--mud-palette-divider, #2B3E44);
        --ffp-accent: var(--mud-palette-primary, #99CFDD);
    }
}

/* The scheme the app is actually in, which is not the same question as the one the media
   query above asks: somebody on a light desktop can pick Dark in the user menu, and from
   that moment the OS preference is the wrong answer. MainLayout writes the attribute from
   ThemePreference.IsDark, which is the only place that knows.

   It is here rather than in MessageList.razor.css because a scoped stylesheet cannot
   define a variable for a surface it does not own, and because the next component that
   needs an amber should find the token already made. */
.ffp-shell[data-scheme="dark"] {
    --ffp-unread: var(--ffp-amber);
}

html, body {
    margin: 0;
    padding: 0;
}

/* The first thing the keyboard reaches, and invisible until it does: without it every
   visit starts with a walk through the whole sidebar (N11, WCAG 2.4.1). */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 2000;
    padding: 0.6rem 1rem;
    background: var(--ffp-petrol);
    color: var(--ffp-weiss);
    border-radius: 0 0 var(--ffp-radius) 0;
}

.skip-link:focus {
    left: 0;
}

/* Text for the screen reader and for nobody else.
 *
 * Not display:none and not visibility:hidden - both take the element out of the
 * accessibility tree as well, which is the opposite of the point. The 1px clip is the
 * old trick because it is the one that survives everything: the text stays in the tree,
 * stays in the reading order, and takes up no room.
 *
 * Global rather than scoped: three components need it now (the composer's attach
 * control, the user menu's activator, the quick switcher's rows), and a scoped copy in
 * each would be three rules that have to agree by hand.
 */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/*
 * Files dragged over a conversation (#780): a dashed frame over the channel's column or
 * the thread panel, with the sentence that says what letting go will do. Global, because
 * composer.js puts it on the body - fixed over the zone, so the zone's own layout is
 * left alone - and a scoped rule would never match it. pointer-events: none keeps the
 * drag events on the zone underneath.
 */
.ffp-drop-overlay {
    position: fixed;
    z-index: 1400;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px dashed var(--ffp-accent);
    border-radius: var(--ffp-radius);
    background: color-mix(in srgb, var(--ffp-bg, #fff) 85%, transparent);
    color: var(--ffp-accent);
    font-weight: 600;
    pointer-events: none;
}

/* A visible focus ring everywhere, including where a component library would rather
   not have one (WCAG 2.4.7). A crisp ring in the brand colour and no outer glow, which
   is what the handbook asks for; on dark the token is the lightened Petrol, because a
   ring has to be seen against the surface it sits on rather than match a swatch. */
:focus-visible {
    outline: 2px solid var(--ffp-accent);
    outline-offset: 2px;
}

/* FocusOnNavigate (App.razor) puts the focus on the page's h1 after every navigation, so
   a screen reader starts reading at the new page. That focus comes from the app, not
   from the keyboard, and a ring around "Anmelden" only looked broken (#674). A heading
   is not an interactive element, so nothing is lost by not drawing it there. */
h1:focus,
h1:focus-visible {
    outline: none;
}

.loading-progress {
    display: block;
    width: 6rem;
    height: 6rem;
    margin: 20vh auto 1rem auto;
}

.loading-progress circle {
    fill: none;
    stroke: var(--ffp-accent);
    stroke-width: 0.4rem;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}

.loading-progress circle:last-child {
    stroke: var(--ffp-accent);
    stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
    transition: stroke-dasharray 0.05s ease-in-out;
}

.loading-progress circle:first-child {
    stroke: var(--ffp-muted);
    opacity: 0.25;
}

.loading-progress-text {
    text-align: center;
    color: var(--ffp-muted);
    font-size: 0.875rem;
}

.loading-progress-text::after {
    content: "Loading " var(--blazor-load-percentage-text, "");
}

#blazor-error-ui {
    display: none;
    position: fixed;
    inset: auto 0 0 0;
    padding: 0.75rem 1.25rem;
    /* The one bar the framework paints itself, so it cannot come from the theme.
       Amber is the handbook's signal colour and Tinte the ink that survives on it at
       6.8:1; the border replaces the drop shadow the design system does not allow. */
    background: var(--ffp-amber);
    color: var(--ffp-tinte);
    border-top: 1px solid var(--ffp-amber-tief);
    /* Above everything MudBlazor draws, and that is the whole point of the number.
       It used to be 1000, which is under the drawer at 1100 - so the one bar in the
       application that says "this window is broken, reload it" was covered by the
       sidebar, with only the tail of the word Reload sticking out past its edge. The
       framework's own scale ends at the tooltip on 1600; this sits clear of it, because
       there is nothing a person needs to see less than a tooltip over a message telling
       them the app has stopped working. */
    z-index: 2000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    float: right;
}

/* --- Phase 0 forms -------------------------------------------------------- */

.field {
    margin-bottom: 1rem;
}

.field label {
    display: block;
    margin-bottom: 0.25rem;
    font-weight: 600;
    color: var(--ffp-fg);
}

.field input {
    width: 100%;
    max-width: 24rem;
    padding: 0.5rem 0.625rem;
    border: 1px solid color-mix(in srgb, var(--ffp-muted) 45%, transparent);
    border-radius: var(--ffp-radius);
    background: var(--ffp-bg);
    color: var(--ffp-fg);
    font: inherit;
}

.field input:focus-visible,
button:focus-visible {
    outline: 2px solid var(--ffp-accent);
    outline-offset: 2px;
}

button {
    padding: 0.5rem 1rem;
    border: 0;
    border-radius: var(--ffp-radius);
    background: var(--ffp-accent);
    color: #fff;
    font: inherit;
    cursor: pointer;
}

button:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

button.link {
    padding: 0;
    background: none;
    color: var(--ffp-accent);
    text-decoration: underline;
}

.error {
    color: #b42318;
    font-weight: 600;
}

dl {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.25rem 1rem;
    margin: 0 0 1rem;
}

dt {
    font-weight: 600;
}

dd {
    margin: 0;
    color: var(--ffp-muted);
    font-variant-numeric: tabular-nums;
}

h2 {
    margin-top: 2rem;
    font-size: 1.125rem;
}

.validation-message {
    display: block;
    margin-top: 0.25rem;
    color: #b42318;
    font-size: 0.875rem;
}

/* --- Message list benchmark ----------------------------------------------- */

.ffp-bare {
    max-width: 60rem;
    margin: 0 auto;
    padding: 2rem 1.5rem;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.benchmark {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    height: 100vh;
    max-width: 60rem;
    margin: 0 auto;
    padding: 1.5rem 1rem;
    box-sizing: border-box;
}

.benchmark header h1 {
    margin: 0 0 0.25rem;
}

.benchmark header p {
    margin: 0;
}

.benchmark .controls {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.benchmark table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.benchmark th,
.benchmark td {
    padding: 0.375rem 0.5rem;
    text-align: left;
    border-bottom: 1px solid color-mix(in srgb, var(--ffp-muted) 25%, transparent);
}

.benchmark td:nth-child(2),
.benchmark td:nth-child(3) {
    font-variant-numeric: tabular-nums;
}

.benchmark .overall {
    margin: 0.75rem 0 0;
    padding: 0.625rem 0.875rem;
    border-radius: 6px;
    font-weight: 600;
}

.benchmark .overall.pass {
    background: color-mix(in srgb, #2b8a3e 18%, transparent);
    color: #2b8a3e;
}

.benchmark .overall.fail {
    background: color-mix(in srgb, #b42318 18%, transparent);
    color: #b42318;
}

.benchmark .small {
    font-size: 0.8125rem;
}

/* The list needs a bounded height, otherwise it grows and virtualisation has nothing
   to virtualise against. */
.benchmark .list-frame {
    flex: 1;
    min-height: 20rem;
    border: 1px solid color-mix(in srgb, var(--ffp-muted) 30%, transparent);
    border-radius: 8px;
    overflow: hidden;
}

/* ---------------------------------------------------------------------------
   What a rendered message looks like (#161, #165).

   Every class here is one RenderedContent.cs hands out. They were all undefined
   until 22.09.2026 except the table: the renderer named its blocks from the
   start and nothing ever styled them, so a message fell back on whatever
   MudBlazor does to a bare <p>, <ul> or <pre>. Nothing looked broken, which is
   why it lasted a design phase - an undefined class is not an error in a
   browser, it is a nothing.

   The values are the ones the Flutter client already reasoned its way to
   (mobile/lib/src/features/messages/rendered_content.dart), because the two
   clients draw the same tree and a message should be the same message in both.
   Where that file explains a choice, the choice is repeated here rather than
   re-decided.
   --------------------------------------------------------------------------- */

/* The 4px DESIGN.md puts between paragraphs of the same sender - above every
   block but the first, so a one-paragraph message carries no stray space. */
.ffp-md-p,
.ffp-md-h,
.ffp-md-quote,
.ffp-md-list,
.ffp-md-pre,
.ffp-md-rule,
.ffp-md-card,
.ffp-md-table-scroll,
.ffp-md-copyable {
    margin: var(--ffp-space-xs) 0 0;
}

.ffp-md-p:first-child,
.ffp-md-h:first-child,
.ffp-md-quote:first-child,
.ffp-md-list:first-child,
.ffp-md-pre:first-child,
.ffp-md-rule:first-child,
.ffp-md-card:first-child,
.ffp-md-table-scroll:first-child,
.ffp-md-copyable:first-child {
    margin-top: 0;
}

.ffp-md-p {
    /* Inherited rather than set: a thread pane and a quoted preview draw the same
       tree smaller, and a size written here would override the one they chose. */
    line-height: 1.45;
}

/* A heading in a message is not a page title. The renderer already draws a div
   with an aria-level two steps down for that reason; this is the visual half -
   larger and in the serif, never the weight of an h1 in a timeline. The scale is
   Flutter's: 1.30, 1.20, 1.12, then body size with a serif face. */
.ffp-md-h {
    font-family: var(--ffp-font-serif);
    font-weight: 700;
    line-height: 1.25;
}

.ffp-md-h1 { font-size: 1.3em; }
.ffp-md-h2 { font-size: 1.2em; }
.ffp-md-h3 { font-size: 1.12em; }
.ffp-md-h4,
.ffp-md-h5,
.ffp-md-h6 { font-size: 1em; }

/* A Slack attachment (#65). The same left-edge idea as a quotation - the content
   came from somewhere else - but with the sender's own colour on it, because on an
   alert the colour is the message: green and red are what a person reads first and
   the words second.

   The accent arrives as --ffp-card-accent and falls back to the border grey when the
   sender named none, so a card without a colour is still a card and not a stray
   indent. The property is set on the element by the renderer and nowhere else. */
.ffp-md-card {
    padding: var(--ffp-space-sm) var(--ffp-space-md);
    border-left: 3px solid var(--ffp-card-accent, var(--ffp-border));
    border-radius: 0 var(--ffp-radius) var(--ffp-radius) 0;
    background: var(--ffp-surface);
}

/* Cards stack. Without this two attachments touch and read as one. */
.ffp-md-card + .ffp-md-card {
    margin-top: var(--ffp-space-sm);
}

/* The heading of a card is its title, and it sits tighter to the text under it
   than a heading in running prose does. */
.ffp-md-card > .ffp-md-h {
    margin-bottom: 2px;
}

/* The 3px Petrol edge DESIGN.md gives the AI-assist block, reused for a
   quotation: both say "this text came from somewhere else". */
.ffp-md-quote {
    padding-left: var(--ffp-space-sm);
    border-left: 3px solid var(--ffp-accent);
    color: var(--ffp-muted);
}

/* A marker column wide enough that the second line of a wrapped item aligns
   under the first word rather than under the bullet. */
.ffp-md-list {
    padding-left: var(--ffp-space-xl);
}

.ffp-md-list li + li {
    margin-top: var(--ffp-space-xs);
}

.ffp-md-list .ffp-md-list {
    margin-top: var(--ffp-space-xs);
}

/* Scrollable rather than wrapped: a wrapped line of code is a line of code that
   has changed meaning, and in a column this narrow almost every line would wrap. */
.ffp-md-pre {
    padding: var(--ffp-space-sm);
    border: 1px solid var(--ffp-border);
    border-radius: var(--ffp-radius);
    background: var(--ffp-surface);
    overflow-x: auto;
}

.ffp-md-pre code {
    font-family: ui-monospace, Menlo, "Courier New", monospace;
    font-size: 0.9em;
    line-height: 1.4;
    /* `language` is recorded on the node and deliberately not highlighted: a
       highlighter is a dependency and a payload, and #94 watches that number. */
    white-space: pre;
}

/* A code block or a quotation with its copy button (#772). The frame carries the
   block's spacing, so the block inside it is its first child and sits flush. The
   button stands in the top right corner and shows itself on hover or focus, like the
   row's own actions; on a touch screen, which has no hover, it is always there. */
.ffp-md-copyable {
    position: relative;
}

/* A quotation's words wrap, so they keep clear of the corner; a code block scrolls
   and has its first line under the button only while the button is shown. */
.ffp-md-copyable > .ffp-md-quote {
    padding-right: 1.75rem;
}

.ffp-copy-button {
    position: absolute;
    top: var(--ffp-space-xs);
    right: var(--ffp-space-xs);
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem;
    border: 1px solid var(--ffp-border);
    border-radius: var(--ffp-radius);
    background: var(--ffp-surface);
    color: var(--ffp-muted);
    font-size: 0.75rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
}

.ffp-md-copyable:hover .ffp-copy-button,
.ffp-copy-button:focus-visible,
.ffp-copy-button.copied {
    opacity: 1;
}

.ffp-copy-button:hover {
    color: var(--ffp-accent);
}

.ffp-copy-button svg {
    width: 0.95rem;
    height: 0.95rem;
    fill: currentColor;
}

.ffp-copy-button.copied {
    color: var(--ffp-accent);
}

@media (hover: none) {
    .ffp-copy-button {
        opacity: 1;
    }
}

/* Tight, not a padded pill. Inline code sits between words and has to keep their
   baseline and their line height; the Flutter side paints exactly the glyph box
   for the same reason. */
.ffp-md-code {
    padding: 0 0.2em;
    border-radius: 2px;
    background: var(--ffp-surface);
    font-family: ui-monospace, Menlo, "Courier New", monospace;
    font-size: 0.9em;
}

.ffp-md-rule {
    margin-bottom: var(--ffp-space-xs);
    border: 0;
    border-top: 1px solid var(--ffp-border);
}

/* A mention, as a small tinted pill.
 *
 * Amber for "this is about you", Petrol for everyone else's. The handbook caps
 * Amber at one anchor per view and a mention of the reader is what it reserves
 * that anchor for; a mention of a third party is information, not a signal.
 *
 * Never Amber as type - the handbook forbids it outright, which is why the tint
 * is on the ground and the letters stay ink. */
.ffp-mention {
    padding: 1px var(--ffp-space-xs);
    border-radius: var(--ffp-radius);
    background: color-mix(in srgb, var(--ffp-accent) 12%, transparent);
    color: var(--ffp-accent);
    font-weight: 600;
}

.ffp-mention-me {
    background: color-mix(in srgb, var(--ffp-amber) 28%, transparent);
    color: var(--ffp-fg);
}

/* A #channel reference (#909). The one that links keeps the pill and says it is a link
 * on hover; one the reader may not see is not a link and does not look like one. */
a.ffp-mention-channel {
    text-decoration: none;
    cursor: pointer;
}

a.ffp-mention-channel:hover,
a.ffp-mention-channel:focus-visible {
    text-decoration: underline;
}

.ffp-mention-hidden {
    background: color-mix(in srgb, var(--ffp-muted) 12%, transparent);
    color: var(--ffp-muted);
    font-style: italic;
}

/* The lock in front of it, the sidebar's private-channel icon at the size of the text. */
.ffp-mention .ffp-mention-lock {
    font-size: 1em;
    width: 1em;
    height: 1em;
    margin-right: 2px;
    vertical-align: -0.125em;
}

/* A link in a message (#731), whichever way it was written: an address typed bare, a
 * markdown link and Slack's <url|text> all arrive as the same node and get this class,
 * and so does an address in a plain-text answer (LinkedText). Before it they took
 * MudBlazor's reset - body colour, no underline - and could only be found by hovering.
 *
 * The colour is the theme's primary, the colour of everything else you can act on:
 * Petrol on light is 9.01:1 on the white canvas and 8.28:1 on Nebel (own messages, the
 * thread pane, cards); the light Petrol on dark is 10.1:1 on the canvas and 9.15:1 on
 * the dark surface. All far above AA's 4.5:1.
 *
 * Colour is not enough on its own, though (WCAG 1.4.1): against the body ink Petrol is
 * only 1.64:1 on light and 1.5:1 on dark, well under the 3:1 a colour-only link would
 * need. So the link is always underlined - thin, in its own colour and set a little below
 * the baseline, so it reads as "this is a link" without crossing the descenders or
 * weighing as much as the text. A mention is a pill and not a link, and keeps its own
 * style.
 *
 * Hover makes the underline twice as thick and lays a 12% tint of the same colour under
 * the words. The text stays at 7.39:1 on the tint over white and 6.81:1 over Nebel, and
 * 7.83:1 and 7.0:1 on the two dark grounds. The keyboard gets the app's focus ring,
 * drawn here again because the global one above loses to MudBlazor's
 * a:focus-visible { outline: none } - a focused link in a message showed nothing.
 *
 * Visited links keep the same colour on purpose: a chat app is not a reading list, and a
 * link that turns purple because somebody else's message once pointed at the same page
 * says nothing useful. The rule below wins over the browser's :visited purple as it is. */
.ffp-link {
    color: var(--ffp-accent);
    text-decoration-line: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: currentColor;
    text-underline-offset: 0.2em;
    border-radius: 2px;
    /* A link that wraps keeps its tint and ring on every line, not only the first. */
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}

.ffp-link:hover {
    text-decoration-thickness: 2px;
    background: color-mix(in srgb, var(--ffp-accent) 12%, transparent);
}

.ffp-link:focus-visible {
    outline: 2px solid var(--ffp-accent);
    outline-offset: 1px;
}

/* ---------------------------------------------------------------------------
   Tables inside a rendered message (#161).
   --------------------------------------------------------------------------- */

.ffp-md-table-scroll {
    /* The scroller, not the table. A wide table has to move on its own; make the
       message column scroll and every other message slides sideways with it. */
    max-width: 100%;
    overflow-x: auto;
    /* The spacing comes from the shared block rule above now, so that a table keeps
       the same rhythm as the paragraphs around it rather than its own. */
}

.ffp-md-table {
    border-collapse: collapse;
    font-size: 0.875rem;
    /* Not width:100%. A two-column table stretched across the timeline puts an
       arm's length between the label and its number. */
    width: auto;
}

.ffp-md-table th,
.ffp-md-table td {
    padding: var(--ffp-space-xs) var(--ffp-space-sm);
    border: 1px solid var(--ffp-border);
    /* The author's alignment arrives as an inline style and wins over this. */
    text-align: left;
    vertical-align: top;
}

.ffp-md-table th {
    background: var(--ffp-surface);
    font-weight: 600;
}

/* The image preview's dialog (#600). The dialog and its content box belong to MudBlazor's
   provider, outside ImageLightbox's scoped styles, so their share lives here: dark in both
   themes, no padding, and the content filling the screen for the picture to be fitted
   into. */
.mud-dialog.ffp-lightbox {
    background: #101D21;
    color: #FFFFFF;
}

.mud-dialog.ffp-lightbox .ffp-lightbox-content {
    display: flex;
    flex: 1;
    min-height: 0;
    padding: 0;
    overflow: hidden;
}

/* The video preview's dialog (#1048): dark, as the image preview's. */
.mud-dialog.ffp-videoplayer {
    background: #101D21;
    color: #FFFFFF;
}

.mud-dialog.ffp-videoplayer .ffp-videoplayer-content {
    display: flex;
    flex: 1;
    min-height: 0;
    padding: 0;
    overflow: hidden;
}

/* The text preview's dialog (#779), the image preview's sibling. Unlike that one it keeps
   the theme's own surface: text is read, not looked at, and white on black for a page of
   prose is the wrong way round in the light theme. */
.mud-dialog.ffp-textviewer .ffp-textviewer-content {
    display: flex;
    flex: 1;
    min-height: 0;
    padding: 0;
    overflow: hidden;
}

/* The reason under a menu item that is greyed out, such as "Channel verlassen" in a
   default channel (#634). Global because MudBlazor renders menus into a popover outside
   the component's scoped styles. */
.ffp-menu-hint {
    display: block;
    max-width: 18rem;
    white-space: normal;
}

/* A theme or language entry in the account menu, with a tick after the one in force
   (#669). The label takes the room so the tick lines up on the right edge of every
   entry; global for the same reason as the hint above. */
.ffp-menu-choice .mud-menu-item-text {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.ffp-menu-choice .ffp-menu-choice-label {
    flex: 1;
}

.ffp-menu-choice .ffp-menu-choice-check {
    color: var(--mud-palette-primary);
}

/* While a side panel's edge is dragged (#774): the resize cursor everywhere, so it does
   not flicker back to an arrow when the pointer runs ahead of the edge, and no text
   selected along the way. Global because wwwroot/js/panels.js sets it on the body. */
body.ffp-resizing {
    cursor: col-resize;
    user-select: none;
}

/* The same while the composer's top edge is dragged (#1196). */
body.ffp-resizing-rows {
    cursor: ns-resize;
    user-select: none;
}
