/*
 * Cinder — "Gruvbox Editorial" (Epic 3.4).
 *
 * The custom properties in :root are the whole theme API: a new theme is new
 * token values (plus, optionally, its own .prose rules). Dark first; the
 * light variant overrides tokens only. No framework, no build step.
 *
 * Muted text is gruvbox fg4 (#a89984) rather than gray (#928374), and the
 * light variant uses gruvbox's faded accents, so every text colour meets
 * WCAG AA (4.5:1) on the surfaces it sits on.
 *
 * Depth (#113) is surface-only: hairline borders, layered shadows and tints
 * mixed from the tokens with color-mix(); no text colour depends on them.
 * Motion is opt-in via prefers-reduced-motion and hover lift is limited to
 * pointer devices.
 */

:root {
  /* Palette */
  --color-bg: #282828;
  --color-surface: #32302f;
  --color-raised: #3c3836;
  --color-rule: #3c3836;
  --color-fg: #ebdbb2;
  --color-heading: #fbf1c7;
  --color-muted: #a89984;
  --color-accent: #fabd2f;
  --color-on-accent: #282828;
  --color-link: #83a598;
  --color-quote-rule: #fe8019;
  --color-quote: #b8bb26;
  --color-mark: #fabd2f;
  --color-on-mark: #282828;
  --color-ghost: #3c3836;
  /* Gruvbox red (#fb4934) is 3.8:1 on the surface; lifted 30% toward fg. */
  --color-error: #f6755a;
  /* Gruvbox green: 6.4:1 on the surface. */
  --color-ok: #b8bb26;
  --color-hairline: color-mix(in srgb, var(--color-rule) 75%, transparent);

  /* Depth and motion */
  --shadow-1: 0 1px 2px rgb(0 0 0 / 0.25), 0 2px 8px rgb(0 0 0 / 0.18);
  --shadow-2: 0 2px 4px rgb(0 0 0 / 0.25), 0 12px 28px rgb(0 0 0 / 0.3);
  --speed: 160ms;

  /* Code is always dark gruvbox, in both schemes: dark0_hard, where
     gruvbox red keywords reach 4.5:1. */
  --code-bg: #1d2021;
  --code-fg: #ebdbb2;
  --code-inline-bg: var(--color-raised);

  /* Type */
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-body: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-prose: 1.125rem;
  --leading: 1.6;

  /* Space and shape (anti-bloat: 16–20px padding, ~12px gaps) */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 2rem;
  --radius: 10px;
  --content-width: 70rem;
  /* Post page cap; `none` lines it up with the homepage (#122). A theme may
     set a measure such as 68ch. */
  --prose-measure: none;
}

@media (prefers-color-scheme: light) {
  :root {
    --color-bg: #fbf1c7;
    --color-surface: #f2e5bc;
    --color-raised: #ebdbb2;
    --color-rule: #ebdbb2;
    --color-fg: #3c3836;
    --color-heading: #282828;
    --color-muted: #665c54;
    --color-accent: #af3a03;
    --color-on-accent: #fbf1c7;
    --color-link: #076678;
    --color-quote-rule: #af3a03;
    --color-quote: #504945;
    --color-mark: #fabd2f;
    --color-on-mark: #282828;
    --color-ghost: #ebdbb2;
    --color-error: #9d0006;
    /* Gruvbox faded green (#79740e) darkened to reach AA on the surface. */
    --color-ok: #66620c;
    --code-inline-bg: var(--color-raised);
    /* Warm grey (gruvbox dark1) shadows, not black, on the cream ground. */
    --shadow-1: 0 1px 2px rgb(60 56 54 / 0.08), 0 2px 8px rgb(60 56 54 / 0.08);
    --shadow-2: 0 2px 4px rgb(60 56 54 / 0.1), 0 12px 28px rgb(60 56 54 / 0.13);
  }
}

/* Base */

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

html {
  color-scheme: dark light;
  accent-color: var(--color-accent);
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

::selection {
  background: color-mix(in srgb, var(--color-accent) 30%, transparent);
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-fg);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: var(--leading);
  overflow-wrap: break-word;
}

a {
  color: var(--color-link);
  text-underline-offset: 0.15em;
}

a:hover {
  color: var(--color-accent);
}

:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Anchor jumps (headings, footnotes, the skip link) clear the sticky masthead. */
:target {
  scroll-margin-block-start: 5rem;
}

h1,
h2,
h3,
h4 {
  color: var(--color-heading);
  line-height: 1.25;
  margin-block: 0 var(--space-3);
  text-wrap: balance;
}

h1 {
  font-size: 1.75rem;
}

h2 {
  font-size: 1.25rem;
}

img {
  max-inline-size: 100%;
  block-size: auto;
}

.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Above the sticky masthead (z-index 10). */
.skip-link {
  position: absolute;
  z-index: 20;
  inset-inline-start: var(--space-4);
  inset-block-start: -3rem;
  padding: var(--space-2) var(--space-3);
  background: var(--color-accent);
  color: var(--color-on-accent);
  border-radius: var(--radius);
}

.skip-link:focus {
  inset-block-start: var(--space-2);
}

.meta {
  color: var(--color-muted);
  font-size: var(--text-sm);
}

/* Empty states (empty blog, no tags, no search results): a soft dashed box,
   a serif glyph, a heading and a muted explanation. */
.empty {
  margin-block: var(--space-4);
  padding: var(--space-6) var(--space-5);
  border: 1px dashed color-mix(in srgb, var(--color-muted) 45%, transparent);
  border-radius: var(--radius);
  background: var(--color-surface);
  text-align: center;
}

.empty .glyph {
  display: block;
  margin-block-end: var(--space-2);
  color: color-mix(in srgb, var(--color-accent) 55%, var(--color-muted));
  font-family: var(--font-body);
  font-size: 2.2rem;
  line-height: 1;
}

.empty h2 {
  margin: 0 0 var(--space-2);
  color: var(--color-heading);
  font-family: var(--font-body);
  font-size: 1.3rem;
}

.empty p {
  max-inline-size: 46ch;
  margin: 0 auto var(--space-2);
  color: var(--color-muted);
}

.empty .tags {
  justify-content: center;
}

/* Masthead: one compact strip, no hero; sticky frosted glass. */

.masthead,
.container,
.site-footer {
  inline-size: 100%;
  max-inline-size: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--space-5);
}

.masthead {
  position: sticky;
  inset-block-start: 0;
  z-index: 10;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-5) var(--space-3);
  border-block-end: 1px solid var(--color-hairline);
  background: color-mix(in srgb, var(--color-bg) 82%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.15);
  backdrop-filter: blur(12px) saturate(1.15);
}

/* No overflow: hidden here (the mockup's glow layer needed it): it would
   clip the phone menu's dropdown. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .masthead {
    background: var(--color-bg);
  }
}

.masthead p {
  margin: 0;
}

.site-name {
  font-size: 1.25rem;
  font-weight: 700;
}

.site-name a {
  color: var(--color-heading);
  text-decoration: none;
}

.tagline {
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.site-nav {
  margin-inline-start: auto;
}

.site-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav a {
  color: var(--color-accent);
  text-decoration: none;
}

.site-nav a:hover {
  text-decoration: underline;
}

/* Phones get the <details> menu instead of the inline list (no JS). */
.nav-menu {
  display: none;
}

.nav-menu summary {
  cursor: pointer;
  color: var(--color-accent);
  list-style: none;
}

.nav-menu summary::-webkit-details-marker {
  display: none;
}

.nav-menu summary::before {
  content: "≡ ";
}

.nav-menu[open] summary::before {
  content: "× ";
}

.nav-menu ul {
  flex-direction: column;
  padding-block: var(--space-2);
}

.container {
  padding-block: var(--space-5) var(--space-6);
}

/* Footer */

.site-footer {
  padding-block: var(--space-4) var(--space-6);
  border-block-start: 2px solid var(--color-rule);
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.footer-links {
  margin: var(--space-3) 0 0;
}

/*
 * Tags: a gentle 6px pill with a muted # prefix (#113 option D), sized for
 * wide glyphs (日本, café-society). Inside a card the pill is recessed (darker
 * than the card); the whole pill warms to the accent on hover.
 */

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding: 0;
  list-style: none;
}

.tags a {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: 0.1em 0.6em;
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: var(--text-sm);
  line-height: 1.5;
  text-decoration: none;
  white-space: nowrap;
}

.card .tags a,
.search-results .tags a,
.empty .tags a {
  background: color-mix(in srgb, var(--color-bg) 65%, var(--color-surface));
}

.tags a::before {
  content: "#";
  opacity: 0.7;
}

.tags a:hover {
  background: color-mix(in srgb, var(--color-accent) 14%, var(--color-surface));
  color: var(--color-fg);
}

.tags a:hover::before {
  color: var(--color-accent);
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .tags a,
  .tags a::before {
    transition:
      background var(--speed) ease,
      color var(--speed) ease;
  }
}

.count {
  color: var(--color-muted);
  font-size: 0.85em;
}

.tag-cloud {
  margin: 0;
}

/* Tags close a card (#123), so they need no space after them. */
.card .tags,
.search-results .tags {
  margin: var(--space-2) 0 0;
}

.tag-index a {
  font-size: var(--text-base);
}

/* Cards (index only) */

.card {
  margin-block-end: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-hairline);
  background: var(--color-surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
}

/* Lift only cards that lead somewhere (not the empty-search card), and only
   for a real pointer, so a tap never leaves a card stuck up. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .card {
    transition:
      box-shadow var(--speed) ease,
      translate var(--speed) ease,
      border-color var(--speed) ease;
  }

  .card:has(.card-title a):hover {
    translate: 0 -2px;
    border-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-rule));
    box-shadow: var(--shadow-2);
  }
}

.card p {
  margin: 0 0 var(--space-2);
}

.card .card-title {
  margin-block-end: var(--space-2);
  font-size: 1.125rem;
}

.card-title a {
  color: var(--color-heading);
  text-decoration: none;
}

.card-title a:hover {
  color: var(--color-accent);
}

.card-excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.card .meta {
  margin: 0;
}

.card-featured {
  margin-block-end: var(--space-3);
}

.card-featured .card-title {
  font-size: 1.5rem;
}

.card-featured .card-excerpt {
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

.card-featured.card-with-hero {
  display: grid;
  grid-template-columns: minmax(12rem, 2fr) 3fr;
  gap: var(--space-5);
  align-items: center;
}

.card-hero {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  max-block-size: 16rem;
  object-fit: cover;
  border-radius: calc(var(--radius) - 2px);
}

.badge {
  display: inline-block;
  padding: 0 0.6em;
  border-radius: 999px;
  background: var(--color-bg);
  color: var(--color-accent);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  margin-block-end: var(--space-6);
}

.card-grid .card {
  margin: 0;
}

/* Year-grouped plain lists: index (below the cards), tag pages, archive */

.page-header {
  margin-block-end: var(--space-5);
}

.page-header h1 {
  margin-block-end: var(--space-1);
}

.page-header .meta {
  margin: 0;
}

.year {
  margin-block-end: var(--space-5);
}

.year h2 {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.year h2 .count {
  padding: 0 0.6em;
  border-radius: 999px;
  background: var(--color-raised);
  font-size: var(--text-sm);
  font-weight: 400;
}

.post-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.post-list li {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 40rem) auto;
  justify-content: start;
  gap: var(--space-3);
  align-items: baseline;
  padding-block: var(--space-1);
}

.post-list time {
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.post-list a {
  text-decoration: none;
}

.post-list a:hover {
  text-decoration: underline;
}

.pagination {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-start: var(--space-5);
}

.pagination a {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  text-decoration: none;
}

.pagination a:hover {
  border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);
  background: color-mix(in srgb, var(--color-accent) 14%, var(--color-surface));
  color: var(--color-fg);
}

.pagination [rel="next"] {
  margin-inline-start: auto;
}

/* Post show */

.post {
  max-inline-size: var(--prose-measure);
  margin-inline: auto;
}

.post-header {
  margin-block-end: var(--space-5);
}

.post-header .meta {
  margin: 0 0 var(--space-2);
}

/* Fluid: 1.6rem on a phone up to 2.25rem, no breakpoint jump. */
.post-header h1 {
  font-size: clamp(1.6rem, 1.1rem + 2.4vw, 2.25rem);
}

.post-hero {
  display: block;
  inline-size: 100%;
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
}

/*
 * Post bodies: styled only through .prose descendants, so Markdown output
 * needs no classes of its own. (Lumis' highlighting classes are renderer
 * output, styled by the gruvbox theme stylesheet.)
 */

.prose {
  font-family: var(--font-body);
  font-size: var(--text-prose);
  line-height: 1.7;
}

.prose > * {
  margin-block: 0 var(--space-4);
}

.prose p {
  text-wrap: pretty;
}

.prose h2,
.prose h3,
.prose h4 {
  margin-block-start: var(--space-6);
  font-family: var(--font-ui);
}

.prose h2 {
  font-size: 1.4rem;
}

.prose h3 {
  font-size: 1.2rem;
}

.prose li + li {
  margin-block-start: var(--space-1);
}

.prose img {
  display: block;
  border-radius: var(--radius);
}

.prose blockquote {
  margin-inline: 0;
  padding: var(--space-2) var(--space-4);
  border-inline-start: 3px solid var(--color-quote-rule);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: color-mix(in srgb, var(--color-surface) 55%, transparent);
  color: var(--color-quote);
  font-style: italic;
}

.prose blockquote > :last-child {
  margin-block-end: 0;
}

.prose hr {
  border: 0;
  border-block-start: 2px solid var(--color-rule);
}

.prose code {
  font-family: var(--font-mono);
  font-size: 0.875em;
}

.prose :not(pre) > code {
  padding: 0.1em 0.35em;
  border-radius: 4px;
  background: var(--code-inline-bg);
}

/* Code blocks: always dark gruvbox, even in light mode; scroll in place. */
.prose pre {
  overflow-x: auto;
  padding: var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--code-bg);
  color: var(--code-fg);
  font-size: 0.95rem;
  line-height: 1.5;
  box-shadow: var(--shadow-1);
}

.prose pre.lumis {
  background: var(--code-bg);
}

/* Lumis' gruvbox comment grey is 4.47:1 on the code background, just short;
   lift it. */
.prose pre .l-comment,
.prose pre .l-comment-documentation,
.prose pre .l-comment-hint {
  color: #a89984;
}

/* Wide tables scroll inside their own box instead of the page. */
.prose table {
  display: block;
  max-inline-size: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  font-family: var(--font-ui);
  font-size: var(--text-base);
}

.prose th,
.prose td {
  padding: var(--space-2) var(--space-3);
  border-block-end: 1px solid var(--color-rule);
  text-align: start;
}

.prose th {
  border-block-end: 2px solid color-mix(in srgb, var(--color-accent) 45%, transparent);
  color: var(--color-muted);
  font-size: var(--text-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.prose tbody tr:hover {
  background: color-mix(in srgb, var(--color-surface) 55%, transparent);
}

/* Unresolved wiki-links (decision 3): plain, muted text, never a link. */
.prose .wikilink-unresolved {
  color: var(--color-muted);
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
  cursor: help;
}

.prose .footnotes {
  font-size: var(--text-base);
  color: var(--color-muted);
}

.prose .anchor {
  text-decoration: none;
}

.prose mark,
mark {
  padding: 0 0.15em;
  border-radius: 3px;
  background: var(--color-mark);
  color: var(--color-on-mark);
}

/* Long unbroken strings never widen the page. */
.prose a,
.post-list a,
.card-title {
  overflow-wrap: anywhere;
}

/* Errors */

.error {
  max-inline-size: 40rem;
  margin: var(--space-6) auto;
  text-align: center;
}

.error-code {
  margin: 0;
  color: var(--color-ghost);
  font-size: clamp(4rem, 18vw, 8rem);
  font-weight: 800;
  line-height: 1;
}

.recovery {
  margin-block: var(--space-6);
}

.recovery h2 {
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.recovery .post-list li {
  display: block;
}

.recovery .tags {
  justify-content: center;
  margin-block-start: var(--space-4);
}

/* Sign-in, setup and other plain forms */

form label {
  display: block;
  margin-block-end: var(--space-1);
}

input[type="email"],
input[type="password"],
input[type="text"],
input[type="search"] {
  inline-size: 100%;
  max-inline-size: 24rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-raised);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-fg);
  font: inherit;
}

button {
  padding: var(--space-2) var(--space-4);
  border: 0;
  border-radius: var(--radius);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* The global :focus-visible ring, plus an accent border so the field and
   its ring read as one object. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  border-color: var(--color-accent);
  outline-offset: 1px;
}

/* Tactile buttons: rise on hover, settle on press. Text-style buttons stay flat. */
@media (prefers-reduced-motion: no-preference) {
  button {
    transition:
      background var(--speed) ease,
      box-shadow var(--speed) ease,
      translate var(--speed) ease;
  }

  button:not(.link, :disabled):hover {
    translate: 0 -1px;
    box-shadow: var(--shadow-1);
  }

  button:not(.link, :disabled):active {
    translate: 0 0;
    box-shadow: none;
  }
}

.footer-blurb {
  margin-block: 0 var(--space-2);
  color: var(--color-muted);
}

/* Breathing room under the tag cloud only; without it the footer's own
   padding is enough (#125). */
.tag-cloud + .footer-blurb {
  margin-block-start: var(--space-5);
}

/* The editor (/manage, Epic 4.1): form beside its preview. */

.editor-page {
  max-inline-size: 110rem;
  margin-inline: auto;
  padding: var(--space-4) var(--space-5);
}

.editor-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
}

.editor-bar h1 {
  margin: 0;
  font-size: 1.25rem;
}

.editor-status {
  display: flex;
  gap: var(--space-3);
  margin: 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.editor-status .pill {
  padding-inline: var(--space-2);
  border-radius: 999px;
  background: var(--color-raised);
  color: var(--color-fg);
}

.flash {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
}

/* The tint is on the border only: a tinted fill would drop the error text
   below AA. */
.flash-error {
  border-color: color-mix(in srgb, var(--color-error) 40%, transparent);
}

.flash-error,
.field-error {
  color: var(--color-error);
}

/* LiveView marks its root with these classes while the socket is down (e.g.
   a rejected origin), so /manage says so without any script of its own. */
.disconnected {
  display: none;
  margin: 0;
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  border-block-end: 2px solid var(--color-error);
  color: var(--color-error);
  font-size: var(--text-sm);
}

body:has([data-phx-main].phx-client-error, [data-phx-main].phx-server-error) .disconnected {
  display: block;
}

.editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-5);
  margin-block-start: var(--space-4);
}

.content-repo {
  max-inline-size: 40rem;
  margin-block-start: var(--space-6);
}

.content-repo .public-key {
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-raised);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-fg);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.host-keys {
  padding-inline-start: var(--space-4);
}

.settings-page .editor-form {
  max-inline-size: 40rem;
}

.editor-form fieldset {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius);
}

.editor-form input[type="number"] {
  inline-size: 8rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-raised);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-fg);
  font: inherit;
}

.editor-form .field {
  margin-block-end: var(--space-3);
}

.editor-form input[type="text"],
.editor-form select,
.editor-form textarea {
  inline-size: 100%;
  max-inline-size: none;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-raised);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-fg);
  font: inherit;
}

.editor-form textarea {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  resize: vertical;
}

/* The field rules above outrank the global focus border; restate it. */
.editor-form :is(input, select, textarea):focus-visible,
.content-repo .public-key:focus-visible {
  border-color: var(--color-accent);
}

.editor-form .hint,
.editor-actions .hint {
  margin: var(--space-1) 0 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.editor-form .confirm {
  margin-block-start: var(--space-2);
}

.field-error {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
}

.editor-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

button.link {
  padding: 0;
  background: none;
  color: var(--color-link);
  font-weight: normal;
  text-decoration: underline;
}

button.secondary {
  background: var(--color-raised);
  color: var(--color-fg);
}

button.secondary:not(:disabled):hover {
  background: color-mix(in srgb, var(--color-raised) 86%, var(--color-fg));
}

.posts-table {
  inline-size: 100%;
  margin-block-start: var(--space-4);
  border-collapse: collapse;
}

.posts-table th,
.posts-table td {
  padding: var(--space-2) var(--space-3);
  border-block-end: 1px solid var(--color-rule);
  text-align: start;
}

.posts-table tbody tr:hover {
  background: color-mix(in srgb, var(--color-surface) 55%, transparent);
}

.posts-table .slug {
  display: block;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.posts-table .state-archived {
  color: var(--color-muted);
}

/* My posts filters: a strip of selects on a surface card. */
.posts-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-3) var(--space-4);
  margin-block-start: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
}

.posts-filter label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.posts-filter select {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-raised);
  border-radius: var(--radius);
  background: var(--color-bg);
  color: var(--color-fg);
  font: inherit;
  font-size: var(--text-base);
}

.posts-filter select:focus-visible {
  border-color: var(--color-accent);
}

.posts-filter .meta {
  margin: 0 0 0 auto;
  align-self: center;
}

/* Sync status (/manage/sync): a health summary (#131), then a card per
   topic (#133); each run's free-form result as key/value rows. */

.sync-summary {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  margin-block-end: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
}

.sync-summary h2 {
  margin: 0 0 var(--space-1);
  font-size: 1.25rem;
}

.sync-summary p {
  margin: 0;
  color: var(--color-muted);
  overflow-wrap: anywhere;
}

/* The icon and the headline both say it; colour only reinforces. */
.sync-icon {
  display: inline-grid;
  flex: none;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border: 2px solid var(--color-muted);
  border-radius: 50%;
  color: var(--color-muted);
  font-weight: 700;
  line-height: 1;
}

.sync-ok {
  border-color: color-mix(in srgb, var(--color-ok) 45%, transparent);
}

.sync-ok .sync-icon {
  border-color: var(--color-ok);
  background: var(--color-ok);
  color: var(--color-bg);
}

.sync-ok h2 {
  color: var(--color-ok);
}

.sync-error {
  border-color: color-mix(in srgb, var(--color-error) 45%, transparent);
}

.sync-error .sync-icon {
  border-color: var(--color-error);
  background: var(--color-error);
  color: var(--color-bg);
}

.sync-error h2 {
  color: var(--color-error);
}

/* The same status on the /manage Sync card (#141), on one line. */
.sync-indicator {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  font-weight: 600;
}

.sync-indicator .sync-icon {
  inline-size: 1.5rem;
  block-size: 1.5rem;
  font-size: var(--text-sm);
}

.sync-indicator.sync-ok {
  color: var(--color-ok);
}

.sync-indicator.sync-error {
  color: var(--color-error);
}

.sync-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: var(--space-3);
}

.sync-card {
  min-inline-size: 0;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
}

.sync-card h2 {
  margin: 0 0 var(--space-2);
  font-size: 1.1rem;
}

.sync-card p {
  margin: 0 0 var(--space-2);
}

.sync-card code {
  overflow-wrap: anywhere;
}

.sync-card-wide {
  grid-column: 1 / -1;
}

/* Long paths scroll inside the card, not the page. */
.table-scroll {
  overflow-x: auto;
}

.table-scroll .posts-table {
  margin-block-start: 0;
}

.sync-result {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--space-1) var(--space-4);
  margin: 0;
  font-size: var(--text-sm);
}

.sync-result dt {
  color: var(--color-muted);
}

.sync-result dd {
  margin: 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

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

.editor-preview {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
  overflow-wrap: anywhere;
}

@media (max-width: 60rem) {
  .editor {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Phones (< ~640px): one column, hero above text, nav in a menu. */

@media (max-width: 40rem) {
  .masthead,
  .container,
  .site-footer {
    padding-inline: var(--space-4);
  }

  .tagline {
    display: none;
  }

  .site-nav .nav-inline {
    display: none;
  }

  .nav-menu {
    display: block;
    position: relative;
  }

  .nav-menu[open] ul {
    position: absolute;
    inset-inline-end: 0;
    z-index: 1;
    min-inline-size: 10rem;
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface);
    border-radius: var(--radius);
    box-shadow: 0 4px 16px rgb(0 0 0 / 0.35);
  }

  .card-featured.card-with-hero {
    display: block;
  }

  .card-featured .card-hero {
    margin-block-end: var(--space-3);
    max-block-size: 12rem;
  }

  .card-grid {
    grid-template-columns: 1fr;
  }

  .card-grid .card-excerpt {
    -webkit-line-clamp: 1;
    line-clamp: 1;
  }

  .post-list li {
    grid-template-columns: 1fr auto;
    gap: 0 var(--space-3);
  }

  .post-list time {
    grid-column: 1 / -1;
  }

  .tag-cloud {
    display: none;
  }

  .prose {
    font-size: var(--text-base);
  }
}

/* Search (Epic 3.5): a GET form on a surface card; results as light cards. */

.search-form {
  display: flex;
  gap: var(--space-2);
  margin-block-end: var(--space-5);
  padding: var(--space-3);
  border: 1px solid var(--color-hairline);
  background: var(--color-surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
}

.search-form input[type="search"] {
  flex: 1;
  max-inline-size: none;
  background: var(--color-bg);
}

.search-results {
  margin: 0;
  padding: 0;
  list-style: none;
}

.search-results > li {
  margin-block-end: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
}

/* Matched words: a translucent wash of the mark colour with heading-colour
   text (AA in both schemes), quieter than the solid highlight in posts. */
.search-results mark {
  background: color-mix(in srgb, var(--color-mark) 25%, transparent);
  color: var(--color-heading);
}

.search-results > li > a {
  font-weight: 600;
  text-decoration: none;
}

.search-results p {
  margin: 0;
}

.search-results .excerpt {
  color: var(--color-muted);
}

/* Same lift as the index cards (#121). */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .search-results > li {
    transition:
      box-shadow var(--speed) ease,
      translate var(--speed) ease,
      border-color var(--speed) ease;
  }

  .search-results > li:hover {
    translate: 0 -2px;
    border-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-rule));
    box-shadow: var(--shadow-2);
  }
}

/* Prev/next at the end of a post: two mini-cards, older on the left. */

.post-nav {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-3);
  max-inline-size: var(--prose-measure);
  margin: var(--space-6) auto 0;
}

.post-nav a {
  display: block;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
  text-decoration: none;
}

.post-nav .next {
  grid-column: 2;
  text-align: end;
}

.post-nav .dir {
  display: block;
  margin-block-end: var(--space-1);
  color: var(--color-muted);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.post-nav .ttl {
  color: var(--color-heading);
  font-family: var(--font-body);
  overflow-wrap: anywhere;
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .post-nav a {
    transition:
      box-shadow var(--speed) ease,
      translate var(--speed) ease,
      border-color var(--speed) ease;
  }

  .post-nav a:hover {
    translate: 0 -2px;
    border-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-rule));
    box-shadow: var(--shadow-2);
  }
}

@media (max-width: 40rem) {
  .post-nav {
    grid-template-columns: minmax(0, 1fr);
  }

  .post-nav .next {
    grid-column: auto;
  }
}

/* Sign-in: one centred card. */

.auth-card {
  max-inline-size: 23.75rem;
  margin: var(--space-6) auto;
  padding: var(--space-5) var(--space-5) var(--space-6);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-2);
}

.auth-card h1 {
  margin: 0 0 var(--space-4);
  font-size: 1.5rem;
}

.auth-card .hint {
  color: var(--color-muted);
}

.auth-card label {
  margin-block: var(--space-4) var(--space-1);
  color: var(--color-muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.auth-card input {
  inline-size: 100%;
  max-inline-size: none;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-bg);
  color: var(--color-fg);
  font: inherit;
}

.auth-card p.hint {
  margin-block: var(--space-1) 0;
  font-size: var(--text-sm);
}

.auth-card code {
  overflow-wrap: anywhere;
}

/* Setup's authenticator step: a recessed group inside the card. */
.auth-card fieldset {
  margin: var(--space-5) 0 0;
  padding: var(--space-3) var(--space-4) var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-bg) 45%, var(--color-surface));
}

/* Floated, so it sits inside the recessed group rather than on its border. */
.auth-card legend {
  float: inline-start;
  inline-size: 100%;
  padding: 0;
  color: var(--color-heading);
  font-weight: 600;
}

/* One unbroken base32 string (copy-paste safe), scaled to fit on one line
   down to a 375px phone; narrower than that it wraps anywhere. */
.auth-card .totp-secret {
  display: block;
  padding: var(--space-2);
  border-radius: var(--radius);
  background: var(--color-bg);
  font-size: min(0.8rem, 3.1vw);
}

/* Dark modules on a light ground in both schemes: scanners expect it. The
   code colours are fixed dark-on-cream, so they serve. */
.auth-card .totp-qr {
  display: block;
  inline-size: min(12rem, 100%);
  margin: var(--space-3) auto;
  border-radius: calc(var(--radius) / 2);
  color: var(--code-bg);
}

.totp-qr-ground {
  fill: var(--code-fg);
}

.auth-card button {
  inline-size: 100%;
  margin-block-start: var(--space-5);
}

/* Editor image upload: a dashed dropzone. LiveView adds
   .phx-drop-target-active while a file is dragged over it. */

.dropzone {
  padding: var(--space-4);
  border: 2px dashed color-mix(in srgb, var(--color-muted) 45%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-surface) 50%, transparent);
  color: var(--color-muted);
  text-align: center;
}

.dropzone label {
  margin-block-end: var(--space-2);
}

.dropzone strong {
  color: var(--color-fg);
}

.dropzone:hover,
.dropzone.phx-drop-target-active {
  border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
  background: color-mix(in srgb, var(--color-accent) 6%, var(--color-bg));
}

@media (prefers-reduced-motion: no-preference) {
  .dropzone {
    transition:
      border-color var(--speed) ease,
      background var(--speed) ease;
  }
}

/* Settings: the menu and footer link rows (#128), label | URL | actions. */

.link-rows {
  margin: 0 0 var(--space-3);
  padding: 0;
  list-style: none;
}

.link-rows li {
  display: grid;
  grid-template-columns: minmax(6rem, 1fr) minmax(0, 2fr) auto;
  gap: var(--space-2);
  align-items: center;
  margin-block-end: var(--space-2);
}

.link-row-actions {
  display: flex;
  gap: var(--space-1);
}

.link-row-actions button {
  padding: var(--space-1) var(--space-2);
}

@media (max-width: 40rem) {
  .link-rows li {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The editor's hero field (#132): a thumbnail, then replace / remove. The
   file input is visually hidden; its label is the button, and takes the
   input's focus ring. */

.hero-field .label {
  display: block;
  margin-block-end: var(--space-1);
}

.hero-thumb {
  display: block;
  max-inline-size: 100%;
  max-block-size: 10rem;
  margin-block-end: var(--space-2);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  object-fit: cover;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

label.button {
  display: inline-block;
  margin: 0;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-weight: 600;
  cursor: pointer;
}

.hero-actions input:focus-visible + label.button {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* /manage landing (#129): its sections as cards, on the index cards' look. */

.manage-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-3);
  margin-block-end: var(--space-6);
}

.manage-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
  color: var(--color-muted);
  text-decoration: none;
}

.manage-card h2 {
  margin: 0;
  color: var(--color-heading);
  font-size: 1.15rem;
}

.manage-card p {
  margin: 0;
}

.manage-card .glyph {
  color: color-mix(in srgb, var(--color-accent) 55%, var(--color-muted));
  font-family: var(--font-body);
  font-size: 1.6rem;
  line-height: 1;
}

.manage-card:hover {
  color: var(--color-muted);
}

.manage-card:hover h2 {
  color: var(--color-accent);
}

/* The posts card's count (#130): a big number, its breakdown beside it. */
.manage-card .stat {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--space-2);
  margin-block: var(--space-1);
}

.manage-card .stat strong {
  color: var(--color-heading);
  font-size: 2rem;
  line-height: 1;
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .manage-card {
    transition:
      box-shadow var(--speed) ease,
      translate var(--speed) ease,
      border-color var(--speed) ease;
  }

  .manage-card:hover {
    translate: 0 -2px;
    border-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-rule));
    box-shadow: var(--shadow-2);
  }
}

/* Sign out stays a separate action, apart from the cards. */
.sign-out {
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-hairline);
}

/* Editor confirmations (#127): a toast at the foot of the viewport, near the
   save buttons, that fades itself out. Each save inserts a new element, so
   the animation replays. Reduced motion: it fades, without the rise. */

.toast {
  position: fixed;
  inset-block-end: var(--space-5);
  inset-inline: 0;
  z-index: 10;
  inline-size: fit-content;
  max-inline-size: calc(100% - 2 * var(--space-4));
  margin: 0 auto;
  border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
  box-shadow: var(--shadow-2);
  color: var(--color-fg);
  pointer-events: none;
  animation: toast-fade 4s ease forwards;
}

@keyframes toast-fade {
  0%,
  85% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    visibility: hidden;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .toast {
    animation-name: toast-rise;
  }
}

@keyframes toast-rise {
  0% {
    opacity: 0;
    translate: 0 0.5rem;
  }

  6%,
  85% {
    opacity: 1;
    translate: 0 0;
  }

  100% {
    opacity: 0;
    visibility: hidden;
  }
}

/* A new post's dropzone (#126): shown so it isn't a surprise, but inert
   until the first save. */

.dropzone-disabled,
.dropzone-disabled:hover {
  border-color: color-mix(in srgb, var(--color-muted) 25%, transparent);
  background: transparent;
  cursor: not-allowed;
}

.dropzone-disabled p {
  margin: 0;
}

.dropzone-disabled strong {
  color: var(--color-muted);
}
