/* Self-hosted: the CSP sets font-src 'self', so a CDN <link> would be blocked. */
/* Variable fonts: Google serves ONE file covering the whole weight axis, so a weight
   RANGE is declared and a single file is shipped per family. Declaring two static
   weights pointing at the same file would download identical bytes twice. */
@font-face {
    font-family: 'Fraunces';
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url('fonts/fraunces-latin.woff2') format('woff2');
}

@font-face {
    font-family: 'Source Sans 3';
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url('fonts/source-sans-3-latin.woff2') format('woff2');
}

/* Our type system ships two weights, 400 and 600. Bold maps to 600 rather than 700:
   the ranged variable faces above stop at 600, and an unmatched 700 request would either
   clamp silently or be faux-bolded by the engine. <strong> is the common case — Markdown
   emphasis in article bodies compiles to it.

   Bootstrap's .fw-bold utility is deliberately NOT covered here: it declares
   font-weight: 700 !important, which no non-important rule can override. Nothing in this
   app uses it today. If that changes, it needs its own !important override rather than a
   silent clamp to the top of the variable range. */
strong, b, th {
    font-weight: 600;
}

:root {
    --sf-ink: #2A211C;
    --sf-muted: #7A6355;
    --sf-faint: #7E634F;
    /* Text accent. Darkened from #D9762B, which reached only 3.08:1 on --sf-page and
       2.53:1 on the hero gradient's warmest stop (#FFE0BE) — both below the 4.5:1 AA
       threshold for normal-size text. This value clears 4.8:1 on the gradient and
       5.8:1 on the page. Use it for anything a reader has to READ.

       Also the colour of the global :focus-visible ring, for the same reason: the ring can
       be drawn on top of --sf-gradient (the home hero), not only on --sf-page. --sf-accent-
       bright measured only 2.53:1 / 2.88:1 there — below the 3:1 WCAG 1.4.11 floor for
       non-text indicators — while this value clears 4.8:1 on the gradient. */
    --sf-accent: #9A4E12;

    /* Decorative accent — the original warm orange. Non-text use ONLY: rules, underlines,
       borders. It does not meet the text contrast threshold, so it must never colour a run
       of text, AND it must not be used for the focus ring either — see --sf-accent below. */
    --sf-accent-bright: #D9762B;
    --sf-surface: #FFFCF9;
    --sf-page: #FFFAF6;

    /* Destructive actions. #B32121 on --sf-page text reaches 6.42:1, clearing the 4.5:1 AA
       threshold for normal-size text with room to spare. */
    --sf-danger: #B32121;

    --sf-radius-card: 16px;
    --sf-radius-pill: 999px;
    --sf-shadow: 0 1px 2px rgba(120, 80, 50, .10), 0 10px 26px rgba(120, 80, 50, .09);
    --sf-gradient: radial-gradient(115% 85% at 15% 0%, #FFE0BE 0%, #FFF1E4 42%, #FFFAF6 100%);

    --sf-display: 'Fraunces', Charter, Georgia, serif;
    --sf-text: 'Source Sans 3', 'Helvetica Neue', Helvetica, Arial, sans-serif;

    /* Type scale, ratio ~1.25, as declared in the visual-identity spec. Every font-size in
       the app resolves to one of these nine steps — no raw rem literals. A size that is not
       on the scale is not a smaller decision than one that is; it is the same decision made
       without the scale, and it is what let 17 distinct sizes accumulate for 9 slots. */
    --sf-text-2xs: .75rem;      /* 12px */
    --sf-text-xs: .8125rem;     /* 13px */
    --sf-text-sm: .875rem;      /* 14px */
    --sf-text-base: 1rem;       /* 16px */
    --sf-text-md: 1.125rem;     /* 18px */
    --sf-text-lg: 1.375rem;     /* 22px */
    --sf-text-xl: 1.75rem;      /* 28px */
    --sf-text-2xl: 2.25rem;     /* 36px */
    --sf-text-3xl: 2.75rem;     /* 44px */

    /* Spacing, on a 4px base. Named by multiple of that base, so --sf-space-6 is 6x4 = 24px:
       the name states the arithmetic and cannot drift from the value.

       The spec's ramp reads 4/8/12/16/24/32/48/64. The 20px and 40px steps below are
       additions: both are legitimate 4px multiples, and the implementation already leaned on
       them 15 times. Snapping those call sites to the nearest spec step would have moved the
       layout visibly to satisfy a ramp, which is the tail wagging the dog. Everything OFF the
       4px base — 6px, 10px, 11px, 18px, 22px gaps — was snapped instead. */
    --sf-space-1: .25rem;       /* 4px  */
    --sf-space-2: .5rem;        /* 8px  */
    --sf-space-3: .75rem;       /* 12px */
    --sf-space-4: 1rem;         /* 16px */
    --sf-space-5: 1.25rem;      /* 20px */
    --sf-space-6: 1.5rem;       /* 24px */
    --sf-space-8: 2rem;         /* 32px */
    --sf-space-10: 2.5rem;      /* 40px */
    --sf-space-12: 3rem;        /* 48px */
    --sf-space-14: 3.5rem;      /* 56px */
    --sf-space-16: 4rem;        /* 64px */
}

html, body {
    font-family: var(--sf-text);
    color: var(--sf-ink);
    background: var(--sf-page);
}

h1, h2, h3, .sf-display {
    font-family: var(--sf-display);
    font-weight: 600;
    letter-spacing: -.015em;
}

a, .btn-link {
    color: var(--sf-accent);
}

/* Bootstrap ships no focus ring on plain links. Keyboard users need one everywhere.
   Uses --sf-accent, not --sf-accent-bright: with outline-offset: 2px the ring can land on
   --sf-gradient (the home hero), where --sf-accent-bright falls below the 3:1 WCAG 1.4.11
   floor for non-text indicators. See the --sf-accent token comment for the measurements. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
summary:focus-visible {
    outline: 2px solid var(--sf-accent);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Shared button layer. Previously redeclared per-page (HomePage, SiteHeader, Login), and the
   copies had already drifted; several other pages used these classes with no rule at all and
   rendered as bare OS-default buttons. One definition here, consumed by every page, works on
   both <a> and <button> — hence the reset of border, font, cursor and text-decoration, since
   the two elements start from different browser defaults. Pages may still add layout-only
   overrides in their own scoped CSS (see .nav-links .cta in SiteHeader.razor.css). */
.cta,
.cta-secondary,
.cta-danger {
    display: inline-block;
    border: 0;
    border-radius: var(--sf-radius-pill);
    padding: var(--sf-space-3) var(--sf-space-5);
    font: inherit;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.cta {
    background: var(--sf-ink);
    color: var(--sf-page);
}

/* Quiet: outlined rather than filled, for secondary actions next to a .cta. */
.cta-secondary {
    background: transparent;
    color: var(--sf-ink);
    border: 1px solid var(--sf-ink);
}

/* Destructive actions — see --sf-danger's token comment for the contrast measurement. */
.cta-danger {
    background: var(--sf-danger);
    color: var(--sf-page);
}

/* Shared form field layer, for the same reason as the button layer above: .form-group was
   declared only in Login.razor.css, so CSS isolation scoped it to that one component and the
   admin article forms — which use the same markup — got nothing. Bootstrap 5 is no fallback
   either: .form-group was a Bootstrap 4 class and does not exist in the 5.3 bundle we load,
   so those labels rendered inline against their inputs with no spacing between fields.
   One definition here, consumed by every form. Pages may still add layout-only overrides in
   their own scoped CSS. */
.form-group {
    margin-bottom: var(--sf-space-4);
}

.form-group label {
    display: block;
    margin-bottom: var(--sf-space-2);
    font-size: var(--sf-text-sm);
    font-weight: 600;
    color: var(--sf-muted);
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    /* 3.7rem is off the spacing ramp on purpose: it clears the warning glyph in the background
       data-URI above, which is positioned 1rem/1.8rem. It tracks that image, not the ramp.
       Unmodified Blazor template CSS, like ReconnectModal.razor.css. */
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "Une erreur s'est produite."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Styles the HTML produced by MarkdownRenderer. Purely presentational: it changes no markup
   and no sanitisation, so the Markdown cycle's security boundary is untouched.
   Lives in app.css rather than in component-isolated CSS because Blazor's scope attribute is
   not applied to markup injected through MarkupString. */
.prose {
    max-width: 68ch;
    font-size: var(--sf-text-md);
    line-height: 1.7;
}

/* The CommonMark pipeline behind MarkdownRenderer can emit <h1> (from a leading "# Title"
   line) and <hr> (from "---") even though today's seeded articles happen not to use either.
   Both need a rule so they don't fall back to browser defaults inside an article body. */
/* The heading ladder moved up one step with the body: .prose was 17px with h3 at 19px, a
   gap of two pixels doing the work of a hierarchy. The body is now 18px (--sf-text-md), so
   h3 has to clear it audibly — hence 22/28/36 rather than 19/24/28. */
.prose h1 {
    font-size: var(--sf-text-2xl);
    margin: var(--sf-space-10) 0 var(--sf-space-3);
}

.prose hr {
    margin: var(--sf-space-10) 0;
    border: none;
    border-top: 1px solid rgba(42, 33, 28, .09);
}

.prose h2 {
    font-size: var(--sf-text-xl);
    margin: var(--sf-space-8) 0 var(--sf-space-3);
}

.prose h3 {
    font-size: var(--sf-text-lg);
    margin: var(--sf-space-6) 0 var(--sf-space-2);
}

.prose p {
    margin: 0 0 var(--sf-space-4);
}

.prose ul,
.prose ol {
    margin: 0 0 var(--sf-space-4);
    padding-left: var(--sf-space-6);
}

.prose li {
    margin-bottom: var(--sf-space-2);
}

.prose blockquote {
    margin: var(--sf-space-6) 0;
    padding: var(--sf-space-1) 0 var(--sf-space-1) var(--sf-space-5);
    border-left: 3px solid var(--sf-accent-bright);
    color: var(--sf-muted);
}

.prose img {
    max-width: 100%;
    height: auto;
    border-radius: var(--sf-radius-card);
}

.prose pre {
    background: #F3EBE4;
    padding: var(--sf-space-4);
    border-radius: 8px;
    overflow-x: auto;
}

.prose code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .9em;
}

.prose pre code {
    background: none;
    padding: 0;
}

/* CatchLink section: the product/privacy/support pages exist in French, English, and
   Spanish, each rendered by its own component file per language — CSS isolation scopes
   rules per file, so a rule written in a single CatchLinkPage.razor.css would not reach
   the English or Spanish page components even though their markup is identical.
   Declared globally here instead: one definition, shared by all 9 pages. */
.catchlink,
.catchlink-privacy,
.catchlink-support {
    max-width: 42rem;
    margin-top: var(--sf-space-10);
}

.coming-soon {
    display: inline-block;
    padding: var(--sf-space-2) var(--sf-space-4);
    border-radius: var(--sf-radius-pill);
    background: var(--sf-surface);
    color: var(--sf-muted);
    font-size: var(--sf-text-sm);
    font-weight: 600;
}

.app-links {
    display: flex;
    gap: var(--sf-space-5);
    margin-top: var(--sf-space-8);
    padding-top: var(--sf-space-6);
    border-top: 1px solid rgba(42, 33, 28, .09);
}

.app-links a {
    font-size: var(--sf-text-sm);
    color: var(--sf-muted);
}

.app-links a:hover {
    color: var(--sf-ink);
}

/* Topic colours. Held here rather than in C# because the CSP sets style-src 'self' with
   no 'unsafe-inline': a colour carried in a style attribute would simply be blocked. The
   class name comes from TopicPresentation.CssClass, which derives it from the URL slug,
   so these selectors cannot drift from the routes.

   Each ink was measured against all three backgrounds it can appear on — its own tint,
   --sf-page and --sf-surface — and the worst case of the four is Architecture on its own
   tint at 4.93:1, against a 4.5:1 AA floor. Lightening an ink or darkening a tint is an
   accessibility regression, not a style tweak. Ratios are pinned by a test. */
.topic-architecture { --topic-ink: #9A4E12; --topic-tint: #F7E5D4; }
.topic-dotnet { --topic-ink: #6B3FA0; --topic-tint: #EFE6F7; }
.topic-artisanat { --topic-ink: #4F6B2A; --topic-tint: #E9EFDD; }
.topic-metier { --topic-ink: #1F5F6B; --topic-tint: #DDEDF0; }

/* The topic name, shown beside the coloured rule. Matches the eyebrow treatment already
   used on the home page so the taxonomy reads as part of the existing system rather than
   as a new kind of ornament. */
.topic-label {
    font-size: var(--sf-text-2xs);
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    /* Falls back to --sf-ink like ArticlesList.razor.css's .row does: .topic-label is a
       global class with no scope guard, so it can in principle be applied without one of
       the .topic-* modifier classes present to define --topic-ink, and an undefined custom
       property resolves to nothing rather than to black. */
    color: var(--topic-ink, var(--sf-ink));
}