/*
 * .SYNOPSIS
 * code.css - inline code and fenced code blocks.
 *
 * .DESCRIPTION
 * The readability problems this replaces (from the web part this project is
 * based on) were: a hard-coded near-black block background in every theme, a
 * text-shadow over the code, an inset black box-shadow, line-height:0 on the
 * <code> element, and line numbers positioned absolutely so they overlapped
 * long lines. None of that is here: colours come from the theme, the gutter is
 * a sticky column that survives horizontal scrolling, and the numbers are CSS
 * counters so "copy" never picks them up.
 *
 * .USAGE
 *   Loaded in this order by MarkstrataWebPart.ts, because later rules
 *   override earlier ones:
 *
 *     base, typography, code, syntax, callouts, tables-lists, extras,
 *     chrome, the three themes, modifiers, print
 *
 * .NOTES
 * Since:     0.0.6
 * Ships in:  the web part bundle
 * Requires:  nothing else in this project
 */

/* ------------------------------------------------------------ inline code */

.strata-content code {
  padding: 0.15em 0.35em;
  border: 1px solid var(--strata-code-inline-border, transparent);
  border-radius: var(--strata-radius-sm);
  background-color: var(--strata-code-inline-bg);
  color: var(--strata-code-inline-text);
  font-family: var(--strata-font-mono);
  font-size: var(--strata-code-font-size);
  font-variant-ligatures: none;
}

/* Links and headings keep their own colour for inline code. */
.strata-content a code { color: inherit; }
.strata-content h1 code,
.strata-content h2 code,
.strata-content h3 code,
.strata-content h4 code { font-size: 0.9em; }

/* ------------------------------------------------------------ code blocks */

.strata-code {
  position: relative;
  margin: 0 0 var(--strata-block-gap);
  border: 1px solid var(--strata-code-border);
  border-radius: var(--strata-code-radius);
  background-color: var(--strata-code-bg);
  overflow: hidden;
}

.strata-code-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px 6px 14px;
  border-bottom: 1px solid var(--strata-code-header-border, var(--strata-border));
  background-color: var(--strata-code-header-bg);
  font-family: var(--strata-font-mono);
  font-size: 0.75em;
  line-height: 1.4;
  color: var(--strata-text-muted);
}

.strata-code-lang {
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.strata-code-filename {
  color: var(--strata-text);
  font-weight: 600;
}

.strata-code-lang + .strata-code-filename::before {
  content: '\00b7';
  margin-right: 8px;
  color: var(--strata-text-faint);
}

.strata-code-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 4px;
}

.strata-code-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  border: 1px solid var(--strata-border);
  border-radius: var(--strata-radius-sm);
  background-color: var(--strata-bg-elevated);
  color: var(--strata-text-muted);
  font-family: inherit;
  font-size: 1em;
  line-height: 1.4;
  cursor: pointer;
  transition: color 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
}

.strata-code-btn:hover {
  background-color: var(--strata-bg-hover);
  border-color: var(--strata-border-strong);
  color: var(--strata-text);
}

.strata-code-btn[data-state='done'] {
  border-color: rgb(var(--strata-color-green));
  color: rgb(var(--strata-color-green));
}

.strata-code-btn[data-state='error'] {
  border-color: rgb(var(--strata-color-red));
  color: rgb(var(--strata-color-red));
}

.strata-code-btn svg {
  width: 12px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* With the header switched off the copy button floats over the block and only
   appears on hover or keyboard focus. */
.strata-code:not(.strata-code--has-header) .strata-code-actions {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.12s ease-in-out;
}

.strata-code:not(.strata-code--has-header):hover .strata-code-actions,
.strata-code:not(.strata-code--has-header):focus-within .strata-code-actions {
  opacity: 1;
}

.strata-code pre {
  margin: 0;
  padding: var(--strata-code-padding);
  overflow-x: auto;
  background-color: transparent;
  counter-reset: strata-line;
  color: var(--strata-code-text);
  font-family: var(--strata-font-mono);
  font-size: var(--strata-code-font-size);
  line-height: var(--strata-code-line-height);
  tab-size: 4;
}

.strata-code pre code {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  white-space: pre;
}

/* One block element per source line. width:max-content + min-width:100% lets
   the sticky gutter stay put while the line scrolls under it. */
.strata-code-line {
  display: block;
  width: max-content;
  min-width: 100%;
  counter-increment: strata-line;
}

.strata-code-ln {
  display: none;
  position: sticky;
  left: 0;
  z-index: 1;
  width: var(--strata-code-gutter-width, 2.5em);
  margin-right: 1em;
  padding-right: 0.75em;
  border-right: 1px solid var(--strata-code-gutter-border, var(--strata-border));
  background-color: var(--strata-code-bg);
  color: var(--strata-code-gutter-text);
  text-align: right;
  user-select: none;
  -webkit-user-select: none;
}

.strata-code-ln::before {
  content: counter(strata-line);
}

.strata-code--numbered .strata-code-ln {
  display: inline-block;
}

.strata-code--numbered pre {
  padding-left: 0;
}

.strata-code--numbered .strata-code-line:hover {
  background-color: var(--strata-code-line-hover, transparent);
}

/* Soft wrap instead of horizontal scrolling. */
.strata-code--wrap pre code,
.strata-code--wrap .strata-code-line {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.strata-code--wrap .strata-code-line {
  width: auto;
}

.strata-code--wrap .strata-code-ln {
  vertical-align: top;
}

/* With numbers shown as well, the text has to keep its wrapped continuations
   clear of the gutter - inline, they start back at the line box's left edge,
   under the numbers. A row does that without a hanging indent, and the gutter
   can stop being sticky here: a wrapped block has nothing scrolling under it.
   min-width on the text is what lets a long unbreakable token wrap instead of
   pushing the line wider than the block. */
.strata-code--wrap.strata-code--numbered .strata-code-line {
  display: flex;
  align-items: flex-start;
}

.strata-code--wrap.strata-code--numbered .strata-code-ln {
  position: static;
  flex: none;
}

.strata-code--wrap.strata-code--numbered .strata-code-line-text {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

/*
 * ------------------------------------------------------------ block height
 *
 * A block is as tall as its code unless a fence, or the page setting, says
 * `short` or `medium`. Those cap it and scroll inside.
 *
 * The caps are counted in lines rather than written in pixels because the
 * three themes disagree about both numbers that decide how tall a line is:
 * GitHub sets 0.85em at line-height 1.45, Obsidian 0.875em at 1.5, VS Code
 * 0.9em at 1.5 on a 15px body rather than 16px, and the code size setting
 * moves the first of those again. Written in pixels a cap would be a different
 * number of lines in every theme. Written this way it is the same number of
 * lines in all of them: line-height is a plain multiplier, 1em inside the
 * <pre> is the code font size, so their product is exactly one rendered line.
 *
 * Ten and twenty-five, measured in a browser at the default text size, as the
 * whole block including its header and padding:
 *
 *   short   229px in GitHub, 238px in Obsidian, 227px in VS Code. Roughly a
 *           paragraph of prose, so a block set short reads as something to
 *           glance at, and a page of them is still a page that scrolls rather
 *           than a stack of things that each scroll.
 *   medium  about 525px, 553px and 530px. Over half the content area of a
 *           laptop window, so a block still has the text before and after it on
 *           screen with it - which is the whole reason to cap one.
 *
 * Both are approximate on purpose, both move with the code size setting as they
 * should, and both are named here rather than in the renderer, because the line
 * height they are counted in is here.
 */
.strata-code {
  --strata-code-short-lines: 10;
  --strata-code-medium-lines: 25;
}

/*
 * Sized by its content for these two, so the cap counts lines and the block's
 * own padding sits outside them. Everything else in this project is
 * border-box, where a ten line cap would be ten lines minus the padding and
 * the last line would be cut in half.
 *
 * The padding below the tenth line is scrollable, so a sliver of the eleventh
 * shows through it. That is left alone: something half visible at the bottom
 * edge is the plainest way of saying there is more under it.
 */
.strata-code--short .strata-code-pre,
.strata-code--medium .strata-code-pre {
  box-sizing: content-box;
  overflow-y: auto;
}

.strata-code--short .strata-code-pre {
  max-height: calc(var(--strata-code-short-lines) * var(--strata-code-line-height) * 1em);
}

.strata-code--medium .strata-code-pre {
  max-height: calc(var(--strata-code-medium-lines) * var(--strata-code-line-height) * 1em);
}

/* On paper nothing scrolls, so a capped block prints whole rather than
   printing the first ten lines and losing the rest. */
@media print {
  .strata-code--short .strata-code-pre,
  .strata-code--medium .strata-code-pre {
    max-height: none;
  }
}

/* Scrollbars inside code blocks should read as part of the block. */
.strata-code pre::-webkit-scrollbar {
  height: 10px;
  width: 10px;
}

.strata-code pre::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: 6px;
  background-clip: content-box;
  background-color: var(--strata-scrollbar-thumb, var(--strata-border-strong));
}

.strata-code pre::-webkit-scrollbar-track {
  background-color: transparent;
}

/*
 * A block whose fence named an address instead of a body, while that address
 * is being fetched and after it has failed.
 *
 * The note sits where the code will be. It is a line of prose rather than a
 * spinner because there is something to say: which server is being waited on,
 * and, if it does not answer, whether the file is missing or the server will
 * not let this page read it. A block that stayed empty and silent would read
 * as a fence the author left blank.
 *
 * Copy is hidden while there is nothing to copy. It comes back with the code.
 */
/*
 * The note, and the room it holds for the code that is coming.
 *
 * A block whose address named a line range already knows how many lines it is
 * about to show, and says so in --strata-code-reserve. Reserving that room
 * before the fetch is what stops the document under the block jumping down
 * when it fills, which for a reader who followed a link to a heading below one
 * means the heading slides away under them. An address with no range reserves
 * nothing, because nothing here can know the length of a file it has not read.
 *
 * The room is measured in the block's own metrics rather than in pixels:
 * line-height is a plain multiplier and 1em here is the code font size, so
 * this is exactly as tall as that many rendered lines will be, in whichever
 * theme is on. The box is sized by its content so the padding lands outside
 * those lines, the way it does around the <pre> that replaces it.
 */
.strata-code-note {
  box-sizing: content-box;
  min-height: calc(var(--strata-code-reserve, 0) * var(--strata-code-line-height) * 1em);
  padding: var(--strata-code-padding);
  color: var(--strata-text-muted);
  font-family: var(--strata-font-mono);
  font-size: var(--strata-code-font-size);
  line-height: var(--strata-code-line-height);
}

/* The <pre> is empty until the code arrives, and an empty one still carries
   its own padding, which doubles the space above the note. */
.strata-code--loading .strata-code-pre,
.strata-code--failed .strata-code-pre {
  display: none;
}

/* Nothing to copy in either state, so nothing offering to. */
.strata-code--loading .strata-code-actions,
.strata-code--failed .strata-code-actions {
  visibility: hidden;
}

.strata-code--failed .strata-code-note {
  color: rgb(var(--strata-color-red));
}

/*
 * Lines called out by a fence, `\`\`\`js {2,4-6}`.
 *
 * The called lines are left at full strength and the rest are faded, rather
 * than the called ones being tinted. A tint has to be a colour, and a colour
 * behind syntax highlighting either fights it or is too faint to see; fading
 * what is not being pointed at says the same thing in every theme and against
 * every palette.
 *
 * The marker is drawn in the gutter area so it lines up whether or not line
 * numbers are on, and the whole row carries the emphasis, so a long line that
 * scrolls sideways stays marked across its width.
 */
.strata-code--calling .strata-code-line {
  opacity: 0.45;
  transition: opacity 0.1s;
}

.strata-code--calling .strata-code-line--called {
  opacity: 1;
  background-color: var(--strata-code-called-bg);
  box-shadow: inset 2px 0 0 var(--strata-code-called-bar);
}

/* Hovering the block shows the whole thing, for reading around the excerpt. */
.strata-code--calling:hover .strata-code-line {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .strata-code--calling .strata-code-line { transition: none; }
}

/*
 * A block that has more to show than it is showing.
 *
 * The cursor is left alone over the code, which is selectable text and should
 * say so. The Expand button in the actions is the affordance, and a click
 * anywhere else in the block opens it too, for the reader who tries that
 * first. The outline is for the keyboard reaching the button, which is what
 * moves focus to the block.
 */
.strata-code--zoomable:focus-within {
  border-color: var(--strata-border-strong);
}

/* On paper there is no hover, so nothing must be faded past reading. */
@media print {
  .strata-code--calling .strata-code-line { opacity: 1; }
}
