/*
  nowrap.net

  Farben und Haltung kommen aus zwei eigenen Bildern, nicht aus einer
  Vorlage: dem alten Favicon - ein <br> mit Umbruchpfeil, in #2b387d, der
  Witz zum Namen - und dem gezeichneten Piraten auf rotem Schiff, aus dem
  das Rot und das Himmelblau stammen. Der Grund ist Zeichenpapier.

  Zwei Schriftrollen, klar getrennt: Monospace trägt alles, was Struktur
  ist - der Handle, die Marken, Repo-Namen, Metadaten, die Statuszeile.
  Serifenlose Schrift trägt die Prosa. `nowrap` ist ein CSS-Wert, der Name
  ist Code, die Beiträge sind Sprache.

  Keine Webfonts. Ein Request an Google Fonts wäre derselbe
  Drittanbieter-Verstoß wie das Twitter-Widget, das hier bewusst fehlt.

  Die Seite nimmt sich die Breite, die sie braucht - wie der Tag, nach dem
  sie heißt. Auf einem 4K-Schirm wird der Handle groß und die Raster
  breiten sich aus, statt in einer Handy-Spalte zu kleben.
*/

:root {
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  --grund: #f4f4f1;
  --blatt: #ffffff;
  --tinte: #16181f;
  --leise: #5f6470;
  --linie: #d9d9d3;

  /*
    Blau und Rot stammen aus der Zeichnung selbst, nicht aus einer
    Farbharmonie: flächengewichtet über die kräftig aufgetragenen Stellen
    gemessen, der Farbton zirkulär gemittelt (355° und 5° ergeben 0°, nicht
    180°). Heraus kamen 213,0° für den Buntstift-Blau und 358,6° für das
    Rot.

    Übernommen ist davon der Ton, nicht die Helligkeit: die ist so weit
    verschoben, bis der Kontrast stimmt - gegen den Seitengrund *und*
    gegen die helleren Karten. Ton und Sättigung bleiben dabei stehen,
    darum sehen die Werte unten der Zeichnung noch ähnlich.

      --indigo      5,54:1   Handle, Repo-Namen, Links
      --rot         4,50:1   das Wort "überfällig"
      --rot-signal  3,01:1   Trennpunkte und Statuspunkte (Grafik, kein Text)

    Das frühere #2b387d stammte aus dem alten <br>-Favicon und lag mit
    230,5° rund 17° neben dem Blau der Zeichnung - sichtbar violettstichig.
  */
  --indigo: #1b61b7;
  --rot: #dd191d;
  --rot-signal: #f95155;

  --zustand-frisch: var(--indigo);
  --zustand-alt: #8a5600;
  --zustand-tot: var(--rot);

  --spur: 0.08em;
  --radius: 4px;
  --luft: clamp(1.25rem, 2.5vw, 2rem);

  --ist-dunkel: 0;
}

/*
  Dunkel gibt es in zwei Wegen, und beide müssen zum selben Ergebnis
  führen: die Voreinstellung des Systems, und die ausdrückliche Wahl über
  den Umschalter. Deshalb stehen die Werte zweimal da - einmal hinter der
  Media-Query (aber nur, solange niemand ausdrücklich "hell" gewählt hat),
  einmal hinter dem Attribut, das der Umschalter setzt.

  --ist-dunkel ist die Auskunft für das Skript: es liest den Wert, statt
  die Media-Query ein zweites Mal in JavaScript nachzubauen.
*/
@media (prefers-color-scheme: dark) {
  :root:not([data-thema="hell"]) {
    --ist-dunkel: 1;

    --grund: #101320;
    --blatt: #171b2a;
    --tinte: #e6e8ef;
    --leise: #939ab0;
    --linie: #262b3d;

    --indigo: #8f9ff0;
    --rot: #ff6b73;
    --himmel: #6fb1ef;

    --zustand-alt: #e0a83c;
  }
}

:root[data-thema="dunkel"] {
  --ist-dunkel: 1;

  --grund: #101320;
  --blatt: #171b2a;
  --tinte: #e6e8ef;
  --leise: #939ab0;
  --linie: #262b3d;

  --indigo: #5395e5;
  --rot: #f04044;
  --rot-signal: #f6090e;

  --zustand-alt: #e0a83c;
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--grund);
  color: var(--tinte);
  font-family: var(--sans);
  font-size: clamp(1rem, 0.95rem + 0.2vw, 1.125rem);
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
}

/*
  Die senkrechten Abstände hängen an der Fensterhöhe, nicht an der Breite.
  Auf einem breiten, aber flachen Schirm - 4K mit Skalierung ist genau das
  - würde ein an vw gekoppelter Abstand mitwachsen und die Seite über die
  Kante schieben. An vh gekoppelt zieht sie sich stattdessen zusammen.
*/
.seite {
  width: min(104rem, 92vw);
  margin-inline: auto;
  padding-block: clamp(1.75rem, 4.5vh, 4.5rem) clamp(1.5rem, 3vh, 3rem);
}

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--indigo);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---- Kopf ------------------------------------------------------------ */

/*
  Zwei Spalten, zwei Zeilen: oben die Zeichnung neben dem Text, darunter
  die Profillinks unter der Zeichnung.

  Die erste Zeile ist so hoch wie die Zeichnung. Der Text darin verteilt
  sich mit space-between über genau diese Höhe - dadurch steht der Handle
  an ihrer Oberkante und die Stichwortzeile an ihrer Unterkante, statt
  irgendwo in der Mitte zu schwimmen.

  Die Spaltenbreite hat eine untere Schranke von 10rem, und die ist keine
  Optik, sondern Arithmetik: darunter passen die beiden Links mit Icon
  nicht mehr nebeneinander und stapeln sich.
*/
.kopf {
  display: grid;
  grid-template-columns: clamp(10rem, 13vw, 11rem) 1fr;
  column-gap: clamp(1.25rem, 3vw, 2.5rem);
  row-gap: 0.875rem;
  align-items: start;
  margin-bottom: clamp(1.75rem, 4.5vh, 4.5rem);
}

/* Die Gruppe bleibt im HTML beisammen, im Raster zählen aber ihre
   Kinder - so liegen Bild und Links in derselben Spalte. */
.kopf-marke { display: contents; }

.kopf-marke > picture { grid-area: 1 / 1; }
.kopf-marke > .links { grid-area: 2 / 1; }

/* Nicht rund beschneiden: die Zeichnung hat oben Himmel und unten Wasser,
   ein Kreis schneidet beides weg. Sie liegt hier wie ein aufgeklebtes
   Blatt - gleicher Radius wie die Karten weiter unten. */
.avatar {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  background: var(--blatt);
  border: 1px solid var(--linie);
}

.kopf-text {
  grid-area: 1 / 2;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 0;
}

/*
  Auf dem Telefon bliebe neben der Zeichnung eine Textspalte von rund
  250 px - zu wenig für die Stichwortzeile. Dort rückt die Marke nach
  oben, quer, und der Text bekommt die volle Breite.
*/
@media (max-width: 34rem) {
  .kopf {
    grid-template-columns: 1fr;
    row-gap: 1.5rem;
  }

  /* Zurück zur Gruppe: Bild und Links stehen hier nebeneinander in einer
     eigenen Reihe, der Text bekommt darunter die volle Breite. */
  .kopf-marke {
    display: flex;
    align-items: flex-end;
    gap: 1.25rem;
  }

  .kopf-text {
    grid-area: auto;
    align-self: auto;
    display: block;
  }

  .avatar { width: 5.5rem; }
}

/*
  Der Handle bricht nicht um - der Name ist die Anweisung. Er skaliert mit
  der Breite des Fensters: auf dem Telefon passt er gerade hinein, auf
  einem grossen Schirm nimmt er sich den Platz, den er braucht.
*/
/*
  Die Größe ist auf die Zeichnung abgestimmt: Handle, Name und
  Stichwortzeile zusammen sind so hoch wie der Avatar daneben breit ist,
  über den ganzen clamp-Bereich hinweg. Wird hier gedreht, muss die Breite
  von .kopf-marke mitgehen.
*/
.handle {
  /* "nowrap" hat keine Oberlängen, also klafft zwischen Zeilenkante und
     sichtbarem Buchstabenrücken die Höhe des Ascenders. Der negative
     Rand holt das zurück, damit das Wort wirklich an der Oberkante der
     Zeichnung beginnt und nicht scheinbar tiefer sitzt. */
  margin: -0.19em 0 0;
  font-family: var(--mono);
  font-size: clamp(2.5rem, 8.5vw, 7.5rem);
  font-weight: 700;
  line-height: 0.85;
  letter-spacing: -0.055em;
  white-space: nowrap;
  color: var(--indigo);
}

.name {
  margin: 0.5rem 0 0;
  font-family: var(--mono);
  font-size: 0.875rem;
  letter-spacing: var(--spur);
  text-transform: uppercase;
  color: var(--leise);
}

/* Die eigene Kurzbeschreibung, als Reihe statt als Satz. */
.stichworte {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1rem;
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  font-size: 0.9375rem;
}

/*
  Der Trennpunkt hängt am Begriff davor, nicht am Begriff danach. Sonst
  rutscht er beim Umbruch an den Zeilenanfang, und eine Zeile begänne mit
  einem Punkt statt mit einem Wort.
*/
.stichwort:not(:last-child)::after {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-left: 0.85rem;
  vertical-align: 0.18em;
  border-radius: 50%;
  background: var(--rot-signal);
}

/* Nebeneinander unter der Zeichnung, in deren Breite. Ihre Reihenfolge
   ist dieselbe wie die der Blöcke weiter unten. */
.links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 1rem;
}

.link {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: var(--spur);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--leise);
  padding-bottom: 3px;
  border-bottom: 1px solid var(--linie);
  transition: color 0.15s, border-color 0.15s;
}

.link:hover { color: var(--indigo); border-bottom-color: var(--indigo); }

.link-icon {
  width: 1.15em;
  height: 1.15em;
  flex: none;
}

/* ---- Marken ---------------------------------------------------------- */

.block { margin-bottom: clamp(1.75rem, 4vh, 3.5rem); }

.marke {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  margin: 0 0 1.125rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--leise);
}

.marke-linie { flex: 1; height: 1px; background: var(--linie); }
.marke-zahl { opacity: 0.75; }

/* ---- Raster ---------------------------------------------------------- */

.raster {
  display: grid;
  gap: var(--luft);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Drei Beiträge nebeneinander, sobald der Platz da ist. */
.raster-beitraege { grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr)); }

/* Repos gehen bis drei Spalten - bei sechs Pins ergibt das zwei volle
   Reihen. Mehr Spalten würden die letzte Reihe zerfasern. */
.raster-repos { grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr)); }

@media (min-width: 76rem) {
  .raster-repos { grid-template-columns: repeat(3, 1fr); }
}

/*
  Ab hier ist genug Breite da, um die beiden Blöcke nebeneinander zu
  stellen statt untereinander. Das nutzt die Fläche eines grossen Schirms
  und spart die Höhe eines ganzen Blocks - auf 4K passt die Seite dann
  ohne Rollbalken.

  Die Leserichtung bleibt dieselbe wie in `reihenfolge`: was oben zuerst
  käme, steht hier links.
*/
@media (min-width: 90rem) {
  .seite {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(2rem, 3.5vw, 4rem);
    align-items: start;
  }

  .kopf, .stand { grid-column: 1 / -1; }

  .block { margin-bottom: 0; }

  /* Bleibt nur ein Block übrig, bekommt er die ganze Breite. */
  .block:only-of-type { grid-column: 1 / -1; }

  /* Jede Spalte steht für sich: die Beiträge untereinander, die Repos zu
     zweit - sonst zerfasert die letzte Reihe. */
  .raster-beitraege { grid-template-columns: 1fr; }
  .raster-repos { grid-template-columns: repeat(2, 1fr); }
}

.karte {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--blatt);
  border: 1px solid var(--linie);
  border-radius: var(--radius);
  padding: 1rem 1.25rem 0.75rem;
  transition: border-color 0.15s, transform 0.15s;
}

.karte:hover { border-color: var(--indigo); }

@media (prefers-reduced-motion: no-preference) {
  .karte:hover { transform: translateY(-2px); }
}

/* Die ganze Kachel klickbar, ohne verschachtelte Links. */
.karte-flaeche {
  flex: 1;
  text-decoration: none;
  color: inherit;
}

.karte-flaeche::after { content: ""; position: absolute; inset: 0; }

.karte-fuss {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.875rem;
  margin: 0.75rem 0 0;
  padding-top: 0.625rem;
  border-top: 1px solid var(--linie);
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--leise);
}

.meta-datum { margin-left: auto; opacity: 0.8; }

/* Der Farbpunkt kommt aus GitHubs eigener Sprachfarbe. */
.meta-punkt::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 0.45em;
  border-radius: 50%;
  background: var(--sprach-farbe, var(--leise));
}

.meta-sterne::before { content: "\2605\00a0"; }

.meta-release {
  padding: 0.1em 0.45em;
  border: 1px solid var(--linie);
  border-radius: 2px;
  color: var(--indigo);
}

.meta-art {
  padding: 0.1em 0.45em;
  border: 1px solid var(--linie);
  border-radius: 2px;
}

.meta-archiv { color: var(--zustand-alt); }

.repo-name {
  margin: 0;
  font-family: var(--mono);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--indigo);
}

.repo-text {
  margin: 0.5rem 0 0;
  font-size: 0.9375rem;
  line-height: 1.5;
}

.beitrag-text {
  margin: 0;
  font-size: 1rem;
  line-height: 1.55;
  white-space: pre-line;
}

.leer {
  margin: 0;
  padding: 1.25rem;
  border: 1px dashed var(--linie);
  border-radius: var(--radius);
  font-size: 0.9375rem;
  color: var(--leise);
}

/* ---- Statuszeile ----------------------------------------------------- */

/*
  Das Signaturelement. Es steht sichtbar auf der Seite, weil eine
  Visitenkarte, die stillschweigend zwei Jahre alte Daten zeigt, schlimmer
  ist als eine, die zugibt, dass ihre Quelle klemmt.

  Die Zeile bricht nicht um. Bei Bedarf scrollt sie seitwärts - passend
  zum Namen der Seite.
*/
.stand {
  margin-top: clamp(1.5rem, 3.5vh, 3.5rem);
  padding-top: 1.25rem;
  border-top: 1px solid var(--linie);
}

.stand-kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.75rem;
}

.stand-titel {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--leise);
  opacity: 0.7;
}

/* Umschalter hell/dunkel. Zeigt immer, wohin der Klick führt, nicht wo
   man gerade ist - ein Knopf, der seinen eigenen Zustand nennt, liest
   sich mehrdeutig. */
.thema {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--linie);
  padding: 0 0 3px;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--leise);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

.thema:hover { color: var(--indigo); border-bottom-color: var(--indigo); }

.thema-nach-hell { display: none; }

:root[data-thema="dunkel"] .thema-nach-hell { display: inline; }
:root[data-thema="dunkel"] .thema-nach-dunkel { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-thema="hell"]) .thema-nach-hell { display: inline; }
  :root:not([data-thema="hell"]) .thema-nach-dunkel { display: none; }
}

.stand-zeile {
  display: flex;
  gap: 2rem;
  white-space: nowrap;
  overflow-x: auto;
  padding-bottom: 0.25rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  scrollbar-width: thin;
}

.quelle {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  cursor: help;
}

.quelle::before {
  content: "";
  align-self: center;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--punkt, var(--leise));
  flex: none;
}

.quelle-name {
  letter-spacing: var(--spur);
  text-transform: uppercase;
  color: var(--tinte);
}

.quelle-alter { color: var(--leise); }

.quelle[data-zustand="frisch"] { --punkt: var(--zustand-frisch); }
.quelle[data-zustand="alt"]    { --punkt: var(--zustand-alt); }
.quelle[data-zustand="tot"],
.quelle[data-zustand="fehlt"]  { --punkt: var(--rot-signal); }

/* Was nicht mehr stimmt, wird auch im Text laut - der Punkt allein ist
   für Farbenblinde keine Information. */
.quelle[data-zustand="alt"] .quelle-alter  { color: var(--zustand-alt); }
.quelle[data-zustand="tot"] .quelle-alter,
.quelle[data-zustand="fehlt"] .quelle-alter { color: var(--zustand-tot); }

.quelle-warnung {
  color: var(--zustand-tot);
  text-transform: uppercase;
  letter-spacing: var(--spur);
  border-bottom: 1px solid currentColor;
}

/* ---- Anlauf ---------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .kopf, .block, .stand {
    animation: auf 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
  }
  .block:nth-of-type(1) { animation-delay: 0.06s; }
  .block:nth-of-type(2) { animation-delay: 0.12s; }
  .stand { animation-delay: 0.18s; }

  @keyframes auf {
    from { opacity: 0; transform: translateY(0.5rem); }
  }
}
