/* ============================================================================
   Article prose — the one type scale shared by the writing surface and every
   reading surface.

   WHY THIS FILE EXISTS
   The editor and the readers used to be styled by separate, hand-synced rule
   sets. They drifted, as hand-synced rule sets do: the readers had no rules at
   all for h1, h3, lists, code, rules or images, so a document that looked right
   while being written did not look right once published. Worse, the difference
   was invisible until someone published something with a list in it.

   Now `.xc-prose` goes on BOTH — on `.ql-editor` via the editor's EditorClass
   parameter, and on each reader's content element — so there is exactly one
   selector per rule and nothing left to keep in step.

   `.xc-prose-masked` is the single deliberate difference: it hides spoilers,
   and only the readers opt in. The editor never does, because a writer has to
   be able to read their own draft. Naming that difference is the point — it
   used to be an accident of which selector list someone remembered to update.

   The scale is the READING surface's, not the editor's: Noto Serif TC at 18px
   on a line height of 2. Sizes go through custom properties so the vertical
   reader's A-/A+ control can rescale the whole thing by setting one value.

   Deliberately linked as its own stylesheet rather than @imported into either
   app.css: both of those go through minifiers, and this is the one sheet both
   surfaces depend on.
   ============================================================================ */

.xc-prose {
    --xc-serif: 'Noto Serif TC', Georgia, serif;
    --xc-sans: 'Noto Sans TC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --xc-mono: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;

    --xc-size: 18px;
    --xc-line: 2;
    --xc-ink: #4A3F35;
    --xc-ink-strong: #46392E;
    --xc-rule: rgba(0, 0, 0, .12);
    --xc-code-surface: rgba(0, 0, 0, .05);

    font-family: var(--xc-serif);
    font-size: var(--xc-size);
    line-height: var(--xc-line);
    color: var(--xc-ink);
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* The editor hardcoded light-mode colours and had no dark support at all, so the
   writing surface stayed light while the rest of the app went dark. Tokenising the
   palette fixes the readers and the editor in the same stroke. */
[data-theme="dark"] .xc-prose {
    --xc-ink: var(--text-body, #C9BDAE);
    --xc-ink-strong: var(--text-strong, #EFE6DA);
    --xc-rule: rgba(255, 255, 255, .16);
    --xc-code-surface: rgba(255, 255, 255, .07);
}

/* --------------------------------------------------------------- blocks */

.xc-prose p {
    margin-block: 0 1.5em;
}

.xc-prose .lead {
    font-size: calc(var(--xc-size) + 1px);
    font-weight: 600;
    color: var(--xc-ink-strong);
}

.xc-prose h1,
.xc-prose h2,
.xc-prose h3 {
    font-family: var(--xc-serif);
    font-weight: 900;
    color: var(--xc-ink-strong);
    line-height: 1.35;
}

/* Anchored on the reader's existing h2 at 23px, with h1 and h3 filled in around it —
   neither existed on the reading surfaces before, so a document with either in it
   rendered at the browser default. */
.xc-prose h1 { font-size: calc(var(--xc-size) + 10px); margin-block: 1.2em .6em; }
.xc-prose h2 { font-size: calc(var(--xc-size) + 5px);  margin-block: 1.3em .7em; }
.xc-prose h3 { font-size: calc(var(--xc-size) + 2px);  margin-block: 1.3em .6em; }

.xc-prose blockquote {
    margin-block: 1.6em;
    margin-inline: 0;
    /* Logical properties keep the rule on the inline-start edge on the horizontal
       surfaces and on the top of the column in vertical-rl. */
    border-inline-start: 4px solid var(--brand);
    background: var(--brand-tint);
    color: var(--text-strong);
    padding-block: 16px;
    padding-inline: 26px;
    border-start-end-radius: 12px;
    border-end-end-radius: 12px;
    font-style: italic;
    font-weight: 600;
    /* Quill stores a quote as bare text with no inner p, so the stack has to be
       restated here rather than inherited from the p rule. */
    font-family: var(--xc-serif);
    font-size: var(--xc-size);
}

.xc-prose pre,
.xc-prose pre.ql-syntax {
    font-family: var(--xc-mono);
    font-size: calc(var(--xc-size) - 2px);
    line-height: 1.5;
    background: var(--xc-code-surface);
    color: var(--xc-ink);
    border-radius: 4px;
    padding: 20px;
    white-space: pre-wrap;
}

.xc-prose code {
    font-family: var(--xc-mono);
    font-size: .9em;
    background: var(--xc-code-surface);
    border-radius: 3px;
    padding: .1em .3em;
}

.xc-prose pre code {
    background: none;
    padding: 0;
}

.xc-prose ol,
.xc-prose ul {
    padding-inline-start: 1.4em;
    margin-block: 0 1.5em;
}

.xc-prose li {
    margin-top: .5em;
}

.xc-prose hr {
    border: none;
    border-top: 1px solid var(--xc-rule);
    margin: 2.5rem auto;
    width: 120px;
}

.xc-prose a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    text-decoration-thickness: 1px;
}

.xc-prose b,
.xc-prose strong { font-weight: 700; }

.xc-prose i,
.xc-prose em { font-style: italic; }

.xc-prose img {
    display: block;
    max-width: 100%;
    height: auto;
    margin-block: 2rem;
    margin-inline: auto;
    border-radius: 10px;
}

/* ------------------------------------------------------------ figures */
/* A picture and the credit under it. The spacing that separates the picture from the
   prose belongs to the figure, so the caption can sit tight beneath its image instead
   of being pushed away by the image's own margin. */

.xc-prose figure {
    margin-block: 2rem;
    margin-inline: 0;
}

.xc-prose figure img {
    margin-block: 0;
    margin-inline: auto;
}

.xc-prose figcaption {
    margin-top: .6rem;
    font-size: .875rem;
    line-height: 1.5;
    text-align: center;
    color: var(--text-body);
    opacity: .75;
}

/* The source link has to read as a link against text that is already dimmed, so it keeps
   its underline rather than relying on colour alone. */
.xc-prose figcaption a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* -------------------------------------------------------------- video */
/* A YouTube video is STORED as nothing but an iframe (see modules/video.js), but nothing renders
   it as one. Editor and readers alike show a facade — the poster frame and a play badge — and the
   iframe below is built only once someone clicks (video-facade.js). One rule set serves both,
   which is the point of .xc-prose.

   UNSCOPED, unlike almost everything else in this file. XiongchaMobile's ViewPostVertical renders
   its body into .vv-vbody / .vv-hp with no .xc-prose wrapper at all, so a scoped facade rule would
   leave that one surface with an unstyled poster. The .xc-video-facade class is specific enough to
   stand alone. */

.xc-video-facade {
    position: relative;
    display: block;
    width: 100%;
    /* aspect-ratio sizes the box from the column width, so the video is responsive with no
       wrapper element — which matters because the wrapper would have to be stored too. */
    aspect-ratio: 16 / 9;
    margin: 2rem 0;
    border-radius: 8px;
    overflow: hidden;   /* the poster is cropped to the box rather than the other way round */
    background: #000;   /* shown while the poster loads, and behind a poster that never does */
    cursor: pointer;
}

/* The poster fills the facade exactly, and !important is doing real work here rather than papering
   over a mistake.

   This is an <img> inside a post body, and a post body is the one place in the app where EVERY
   surface asserts something about images: `.xc-prose img` gives them a 2rem margin and
   `height: auto`, `.cp-body .ql-editor img` in posts.css sets `height: auto` again at a higher
   specificity, and the mobile and app-level sheets have their own. Those rules are right about
   pictures in prose and wrong about this one, which is not a picture in prose — it is the surface
   of a video box whose geometry is fixed by the box. Left to the cascade it lost twice, in two
   different ways on two different surfaces: the margin pushed it down and left a black band above
   it, and `height: auto` pushed it out of the bottom.

   Out-specifying each of those selectors would mean editing this rule every time a surface adds an
   image style. Declaring the box non-negotiable says what is actually meant and ends the argument
   for good. Only the geometry is forced; colour, transition and anything else stay ordinary. */
.xc-video-facade .xc-video-poster {
    position: absolute !important;
    inset: 0 !important;
    display: block !important;
    width: 100% !important;
    max-width: none !important;
    height: 100% !important;
    margin: 0 !important;
    /* cover, not contain: hqdefault — the fallback for videos with no maxresdefault — is 4:3 with
       letterboxing baked in, and cropping it to 16/9 is what takes those black bars back off. */
    object-fit: cover !important;
    border: 0;
    border-radius: 0;   /* the facade already clips to its own corners with overflow: hidden */
}

/* YouTube's own proportions: a 68x48 rounded rectangle with a triangle in it. Drawn rather than
   fetched, so the badge costs nothing and cannot fail to load. Scoped through the facade like the
   poster, and given a z-index because both are positioned — without one the badge's place in the
   stack would rest on nothing more than it coming second in the markup. */
.xc-video-facade .xc-video-play {
    position: absolute;
    z-index: 1;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 68px;
    height: 48px;
    border-radius: 12px;
    background: rgba(18, 18, 18, .78);
    transition: background .15s ease;
}

.xc-video-facade .xc-video-play::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-40%, -50%);   /* -40%, because a triangle's optical centre is not its box's */
    border-style: solid;
    border-width: 9px 0 9px 15px;
    border-color: transparent transparent transparent #fff;
}

.xc-video-facade:hover .xc-video-play,
.xc-video-facade:focus-visible .xc-video-play {
    background: #f00;   /* YouTube red, and the only hover affordance the facade needs */
}

.xc-video-facade:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
}

/* While writing, the facade is a picture of a video and nothing more: the click cannot reach it,
   and falls through to Quill, which selects the block so it can be deleted. .bq-root is the editor
   wrapper and is absent on readers, where the same facade is the play button. video-facade.js
   makes the same check — this is the visible half of that pair. */
.bq-root .xc-video-facade {
    pointer-events: none;
    cursor: default;
}

/* The player, once a reader has asked for one. Same box as the facade it replaced, so the article
   does not reflow around the click. */
.xc-prose iframe.xc-video,
iframe.xc-video {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    height: auto;
    margin: 2rem 0;
    border: 0;
    border-radius: 8px;
    background: #000;   /* shown for the instant before YouTube paints its first frame */
}

/* ----------------------------------------------------------- spoilers */
/* Both spoiler elements are unknown to HTML, so they default to display:inline with
   no user-agent rule to fall back on. The card has to declare its own box, in a
   sheet that is always present — an unstyled card would simply show its contents. */

.xc-prose spoiler {
    position: relative;
    display: block;
    margin-block: 1.35rem;
    margin-inline: 0;
    padding-block-start: 1.15rem;
    padding-inline: .875rem;
    padding-block-end: .875rem;
    border: 1px solid var(--spoiler-border);
    border-radius: 12px;
    background: var(--spoiler-surface);
    color: var(--text-body);
}

/* One card, however many lines SHIFT+ENTER put in it. The lines stay ordinary block
   elements — a <p> stays a <p>, a heading stays a heading — so this collapses the
   margins they arrive with rather than any margin of ours. */
.xc-prose spoiler > * {
    margin: 0;
}

.xc-prose spoiler-inline {
    padding: 0 .15em;
    border-radius: 3px;
    background: var(--spoiler-surface);
    box-shadow: 0 0 0 1px var(--spoiler-border);
}

/* The label chip reads --bq-spoiler-label, set on the content element by the page, so
   it stays localized without rewriting the stored HTML. A card carrying its own
   data-spoiler-label overrides it, which keeps labels written by the BotsManager
   editor working. */
.xc-prose spoiler::before {
    content: var(--bq-spoiler-label, "Spoiler");
    position: absolute;
    /* block-start is the top horizontally and the RIGHT edge in vertical-rl;
       inline-start is the left, and the top in vertical-rl. So the chip sits on the
       edge the reader starts from either way, rather than stranding itself. */
    inset-block-start: -.58rem;
    inset-inline-start: .75rem;
    padding-block: .1rem;
    padding-inline: .4rem;
    border-radius: 999px;
    background: var(--spoiler-label-bg);
    color: var(--spoiler-label-text);
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .02em;
    line-height: 1.2;
    font-family: var(--xc-sans);
}

.xc-prose spoiler[data-spoiler-label]::before {
    content: attr(data-spoiler-label);
}

/* ------------------------------------------------- masking (readers only) */
/* text-shadow rather than a filter so the card's own border and background stay
   crisp; user-select stops the hidden words being dragged out of the page. */

.xc-prose-masked spoiler:not(.bq-revealed),
.xc-prose-masked spoiler-inline:not(.bq-revealed) {
    color: transparent;
    text-shadow: 0 0 .72rem var(--spoiler-mask);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
}

/* A descendant that sets its own colour punches straight through the inherited mask,
   which left headings inside a card fully legible. Only colour needs forcing —
   text-shadow is inherited, so the blur already reaches these. */
.xc-prose-masked spoiler:not(.bq-revealed) *,
.xc-prose-masked spoiler-inline:not(.bq-revealed) * {
    color: transparent;
}

/* The chip must stay legible while the card's text is hidden. */
.xc-prose-masked spoiler:not(.bq-revealed)::before {
    color: var(--spoiler-label-text);
    text-shadow: none;
}

/* Links inherit colour, so they would otherwise stay readable through the mask. */
.xc-prose-masked spoiler:not(.bq-revealed) a,
.xc-prose-masked spoiler-inline:not(.bq-revealed) a {
    color: transparent;
    pointer-events: none;
}

/* An image inside a spoiler gives the whole thing away, and text-shadow cannot touch it. */
.xc-prose-masked spoiler:not(.bq-revealed) img {
    filter: blur(18px);
}

/* --------------------------------------------------------- narrow screens */
@media (max-width: 740px) {
    .xc-prose {
        --xc-size: 17px;
        --xc-line: 1.9;
    }
}
