/* Recently — public profile, post and document pages.

   These pages are user-themeable: everything reads from the unprefixed --bg,
   --fg, --accent set, which the data-theme attribute on <html> swaps. Product
   chrome (landing, auth, editor, dashboard) deliberately uses the separate
   --app-* tokens so a user's theme can never restyle the application UI.

   Sans-serif throughout, including the themes -- the old "paper" theme's
   Georgia stack is gone. */

:root {
  --bg: #ffffff;
  --fg: #0d0d12;
  --muted: #5b5f6e;
  --border: #e8e9f0;
  --surface: #f4f5f9;
  --sidebar: #e9eaf1;
  --accent: #6d4aff;
  --accent-fg: #ffffff;
  --radius: 16px;
  --radius-sm: 10px;
  --font: "Inter Var", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-heading: "Space Grotesk", var(--font);
  --width: 36rem;
  --shadow: 0 4px 16px rgba(13, 13, 18, 0.08);
}

[data-theme="ink"] {
  --bg: #0d0d12; --fg: #f2f2f5; --muted: #9096a3; --border: #24242e;
  --surface: #17171f; --sidebar: #101017; --accent: #c2f04c; --accent-fg: #0d0d12;
  --shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}

/* Formerly a cream/serif theme. Now the loud one: lime on off-white with a
   violet accent. Key unchanged so existing profiles need no migration. */
[data-theme="paper"] {
  --bg: #fbfbf4; --fg: #14140f; --muted: #5c5c50; --border: #e3e3d2;
  --surface: #eef5d8; --sidebar: #e2ebc6; --accent: #6d4aff; --accent-fg: #ffffff;
}

[data-theme="midnight"] {
  --bg: #0b0b1a; --fg: #e6e6f5; --muted: #8b8fb0; --border: #1e1e3a;
  --surface: #14142b; --sidebar: #0f0f22; --accent: #8b6cff; --accent-fg: #ffffff;
  --shadow: 0 4px 20px rgba(0, 0, 0, 0.6);
}

[data-theme="terminal"] {
  --bg: #04140c; --fg: #b9f5c8; --muted: #5f8f6c; --border: #10361f;
  --surface: #08200f; --sidebar: #06190c; --accent: #22c55e; --accent-fg: #04140c;
  --font: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --font-heading: var(--font);
  --radius: 4px;
  --radius-sm: 4px;
  --shadow: none;
}

[data-theme="sunset"] {
  --bg: #fff8f6; --fg: #2a1116; --muted: #8a5f66; --border: #ffdfd7;
  --surface: #ffeae4; --sidebar: #f7dcd5; --accent: #ff5c7a; --accent-fg: #ffffff;
  --shadow: 0 4px 16px rgba(255, 92, 122, 0.16);
}

[data-theme="mint"] {
  --bg: #f5fdfb; --fg: #0a2620; --muted: #4d7a70; --border: #cdeee6;
  --surface: #dff7f1; --sidebar: #d2eee7; --accent: #0d9488; --accent-fg: #ffffff;
  --shadow: 0 4px 16px rgba(13, 148, 136, 0.14);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--font-heading);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

a { color: inherit; }

/* --- profile ------------------------------------------------------------- */

.profile, .post, .archive, .doc {
  max-width: var(--width);
  margin: 0 auto;
  padding: 3rem 1.25rem 4rem;
}

.profile-header { text-align: center; }

/* The ring lives on the wrapper so it looks identical whether the photo
   loaded or the initial is showing through. */
.avatar-wrap {
  position: relative;
  width: 96px; height: 96px;
  margin: 0 auto;
  border-radius: 50%;
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 6px var(--accent);
}

.avatar-initial {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--surface);
  font-family: var(--font-heading);
  font-size: 2.25rem; font-weight: 700;
  color: var(--accent);
}

.avatar {
  position: relative;
  display: block;
  width: 96px; height: 96px;
  border-radius: 50%;
  object-fit: cover;
}

.profile-name { margin: 1.5rem 0 .375rem; font-size: 2rem; }
.profile-bio { margin: 0 0 .375rem; color: var(--fg); }
.profile-location { margin: 0; color: var(--muted); font-size: .875rem; font-weight: 500; }

/* Icons must not shrink -- flex would squash the circles into ovals once a few
   platforms are connected -- so they wrap onto a second row instead. */
.socials {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  justify-content: center;
  margin-top: 1.25rem;
}

.socials a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
  transition: background .15s ease, color .15s ease, transform .15s ease;
}

.socials a svg { flex: none; }

.socials a:hover {
  background: var(--accent);
  color: var(--accent-fg);
  transform: translateY(-2px);
}

/* The Now block uses the same open, bordered surface as the link cards; its
   accent label and live dot provide the emphasis. */
.now {
  position: relative;
  margin: 2.25rem 0;
  padding: 1.125rem 1.25rem;
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  overflow: visible;
}

.now-label {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  font-size: .6875rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent);
}

.now-label::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
}

.now-text { margin: .5rem 0 .25rem; font-size: 1.125rem; font-weight: 500; }
.now-time { font-size: .75rem; color: var(--muted); font-weight: 500; }

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

/* Blocks sit on each profile's effective grid (the container carries
   is-cols-2 or is-cols-4). Widths are stored in
   quarter-columns and Python emits the already-resolved span as is-w{n}, so
   this stylesheet never does the arithmetic.

   Placement is auto-flow: order is the only stored input.

   --block-row is the height of one grid row, tuned per breakpoint to sit near
   the column width so a 1x1 reads square. The grid itself stays content-sized
   because headings are labels, while link cards lock to their selected row span
   so media cannot stretch neighboring cards. */
.blocks {
  --block-row: 4.5rem;
  --block-gap: .625rem;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: auto;
  gap: var(--block-gap);
  margin: 2rem 0;
}

/* On a phone the four-column grid is clamped to two columns, so its cells are
   ~166px and the row grows to match -- a Small stays square rather than
   flattening into a button. Left alone at two columns, where the 4.5rem row is
   what every existing page was laid out against. Equal specificity to the
   breakpoint overrides below, which come later and win. */
.blocks.is-cols-4 { --block-row: 10.375rem; }

/* Every span is clamped to two columns below 45rem, so the phone grid is the
   two-column grid whatever the setting says. The four-column rules live in the
   desktop breakpoint; here is-w3/is-w4 are simply full width. */
.block { min-width: 0; min-height: var(--block-row); }
.block.is-w1 { grid-column: span 1; }
.block.is-w2, .block.is-w3, .block.is-w4 { grid-column: span 2; }

/* Height spans: n rows plus the gaps between them. Clamped to two rows on
   phones for the same reason the widths are -- a 4-row tower on a 390px screen
   is a scroll trap, not a layout. */
.block.is-h1 { grid-row: span 1; }
.block.is-h2, .block.is-h3, .block.is-h4 {
  grid-row: span 2;
  min-height: calc(2 * var(--block-row) + var(--block-gap));
}

.block-heading { min-height: 0; }

.block-link {
  display: flex;
  align-items: center;
  gap: .875rem;
  overflow: hidden;
  padding: 1rem 1.125rem;
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  background: transparent;
  transition: border-color .12s ease, transform .12s ease, box-shadow .12s ease;
}

.block-link.is-h1 { height: var(--block-row); }
.block-link.is-h2,
.block-link.is-h3,
.block-link.is-h4 {
  height: calc(2 * var(--block-row) + var(--block-gap));
}

.block-link:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}

.block-link.is-highlighted {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-fg);
}

/* Image layouts promote the stored icon_url to a real image, which is why no
   data migration was needed. The "text" layout renders no image at all, so a
   link that carries an icon_url can still be shown as pure text. */
.block-icon {
  border-radius: 8px;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  object-fit: cover;
}

.block-layout-image_left.has-image { flex-direction: row; }
.block-layout-image_right.has-image { flex-direction: row-reverse; }

.block-layout-image_top.has-image {
  flex-direction: column;
  align-items: stretch;
  gap: .75rem;
}

/* Once a link carries a real image the card is mostly that image, so the
   padding drops to a thin frame and the artwork takes the full tile height.
   A fixed thumbnail left most of a 112px tile empty. */
.block-layout-image_left.has-image,
.block-layout-image_right.has-image,
.block-layout-image_top.has-image {
  align-items: stretch;
  gap: .25rem;
  padding: .6875rem;
}

/* Inset so the corners stay concentric with the card's own radius. The floor
   keeps the small-radius themes (terminal, at 4px) from computing a negative
   value, without ever exceeding the card's own corner. */
.block-layout-image_left.has-image > .block-icon,
.block-layout-image_right.has-image > .block-icon,
.block-layout-image_top.has-image > .block-icon {
  border-radius: max(4px, calc(var(--radius) - .6875rem));
}

/* Square, sized to the standard tile's inner height so it fills the card with
   just the frame showing. Deliberately not stretched: when a neighbour makes
   the row taller, a stretched side image degenerates into a 100x277 strip. */
.block-layout-image_left.has-image > .block-icon,
.block-layout-image_right.has-image > .block-icon {
  flex: none;
  align-self: center;
  width: 3.125rem;
  height: 3.125rem;
}

.block-layout-image_top.has-image > .block-icon {
  flex: 1;
  width: 100%;
  height: auto;
  min-height: 0;
}

.block-layout-image_only.has-image {
  align-items: stretch;
  padding: .375rem;
}

.block-layout-image_only.has-image > .block-icon {
  align-self: stretch;
  flex: 1;
  width: 100%;
  height: auto;
  border-radius: max(4px, calc(var(--radius) - .375rem));
  background: var(--bg);
  object-fit: contain;
}

@media (max-width: 44.9375rem) {
  /* Side-by-side image and copy need the full phone width. Vertically stacked,
     text-only and image-only cards keep the width selected in the editor. */
  :is(.block-layout-image_left, .block-layout-image_right).has-image {
    grid-column: span 2;
  }

  /* Narrow columns make titles and descriptions wrap more often. Keep the
     selected row size as a floor, but let one-row cards grow with their copy
     instead of clipping it against the fixed tile height. */
  .block-link.is-h1 {
    height: auto;
    min-height: var(--block-row);
  }

  /* A side-by-side card now owns the full row, so it no longer needs the square
     minimum used by half-width cells in a four-column profile. */
  :is(.block-layout-image_left, .block-layout-image_right).has-image.is-h1 {
    min-height: 6rem;
  }

  /* Full-width mobile cards have enough room for useful side media. */
  :is(.block-layout-image_left, .block-layout-image_right).has-image > .block-icon {
    align-self: stretch;
    width: min(42%, 10rem);
    height: auto;
  }
}

/* A desktop 1x1 on the four-column grid is a ~160px cell, so side media and
   text still need to stack there. Mobile side-image cards are full-width. */
@media (min-width: 45rem) {
  .blocks.is-cols-4
    > .block-layout-image_left.has-image.is-w1:is(.is-h2, .is-h3, .is-h4),
  .blocks.is-cols-4
    > .block-layout-image_right.has-image.is-w1:is(.is-h2, .is-h3, .is-h4) {
    flex-direction: column;
    text-align: center;
    gap: .5rem;
  }

  .blocks.is-cols-4
    > .block-layout-image_left.has-image.is-w1:is(.is-h2, .is-h3, .is-h4)
    > .block-body,
  .blocks.is-cols-4
    > .block-layout-image_right.has-image.is-w1:is(.is-h2, .is-h3, .is-h4)
    > .block-body {
    flex: none;
    align-items: center;
  }

  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image.is-w1:is(.is-h2, .is-h3, .is-h4)
    > .block-icon {
    align-self: stretch;
    flex: 1;
    width: 100%;
    height: auto;
    min-height: 0;
  }

  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image.is-w1.is-h1
    .block-desc {
    display: none;
  }
}

/* The card's own padding is now just a frame for the image, so the text keeps
   its inset here. Applied on both sides rather than only away from the image:
   a link can be set to an image layout without ever getting an image, and that
   text would otherwise sit against the frame.

   `flex: 1` matters for image_right: without it the body has no grow factor,
   so in a row-reverse flex line both children pack against the right edge and
   the text reads as right-aligned. Growing the body pins the image to the
   right while the text stays left-aligned. */
.block-layout-image_left.has-image > .block-body,
.block-layout-image_right.has-image > .block-body,
.block-layout-image_top.has-image > .block-body {
  justify-content: center;
  padding: .25rem .5rem;
}

.block-layout-image_left.has-image > .block-body,
.block-layout-image_right.has-image > .block-body { flex: 1; }

.block-layout-image_top.has-image > .block-body {
  flex: none;
  padding: .25rem .375rem .375rem;
}

.block-body { display: flex; flex-direction: column; min-width: 0; }
.block-title,
.block-desc {
  display: -webkit-box;
  overflow: hidden;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
}
.block-title {
  flex-shrink: 0;
  font-weight: 600;
  -webkit-line-clamp: 2;
}
.block-desc {
  font-size: .8125rem;
  color: var(--muted);
  -webkit-line-clamp: 6;
}
.is-highlighted .block-desc { color: inherit; opacity: .8; }

/* Headings label the group below them, so they need more room above than the
   grid gap gives -- otherwise the label reads as attached to the preceding
   group. Padding rather than margin because the label bottom-aligns in its
   cell, so the extra height lands above the text either way. Skipped on the
   first block, where the grid's own top margin already does the job. */
.block-heading {
  margin: 0;
  align-self: end;
  padding-bottom: .25rem;
  font-size: .75rem; text-transform: uppercase; letter-spacing: .12em;
  font-weight: 700; color: var(--muted);
}

.block-heading:not(:first-child) { padding-top: 1.25rem; }

.block-text {
  font-size: .9375rem;
  padding: 1rem 1.125rem;
  background: var(--surface);
  border-radius: var(--radius);
  overflow-wrap: anywhere;
}

.block-text p:first-child { margin-top: 0; }
.block-text p:last-child { margin-bottom: 0; }

.post-cover, .post-card-cover {
  width: 100%; border-radius: var(--radius); display: block;
}

/* Sized image blocks fill their cell so the grid stays tidy; a full-width 2x1
   image keeps its natural proportions, as it did before the grid existed. The
   frame gives light photos an edge so they don't bleed into the page. */
.block-image {
  display: flex;
  flex-direction: column;
  /* figure carries a default 1em/40px margin, which insets the whole tile and
     makes it read as smaller than the embed beside it. */
  margin: 0;
  padding: .375rem;
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
}

.block-image img {
  width: 100%;
  border-radius: max(0px, calc(var(--radius) - .375rem));
  display: block;
  flex: 1;
  min-height: 0;
  object-fit: cover;
}

.block-image a { display: contents; }

.block-image figcaption {
  font-size: .8125rem;
  color: var(--muted);
  margin-top: .5rem;
  padding: 0 .25rem .125rem;
}

.block-embed { display: flex; }

.block-embed iframe {
  width: 100%; aspect-ratio: 16 / 9; border: 0;
  border-radius: var(--radius); display: block;
}

/* Once an embed is given a cell taller than 16:9 it should fill it rather than
   leave a gap under the player. */
.block-embed.is-h2 iframe,
.block-embed.is-h4 iframe { aspect-ratio: auto; height: 100%; }

/* Loaded from our own endpoint after the profile renders, so an upstream delay
   never holds up the rest of the page. */
.block-github {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: .75rem;
  padding: 1rem;
  min-height: 10.5rem;
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  overflow: hidden;
}

.github-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.github-title {
  margin: 0;
  font-size: .9375rem;
  line-height: 1.3;
}

.github-summary,
.github-unavailable {
  margin: .2rem 0 0;
  color: var(--muted);
  font-size: .75rem;
  line-height: 1.4;
}

.github-profile-link {
  flex: none;
  color: var(--muted);
  font-size: .75rem;
  font-weight: 650;
  text-decoration: none;
}

.github-profile-link:hover { color: var(--accent); }

.github-scroll {
  overflow-x: auto;
  scrollbar-width: thin;
  overscroll-behavior-inline: contain;
}

.github-graph {
  display: flex;
  gap: 3px;
  width: max-content;
  min-width: 100%;
}

.github-week {
  display: grid;
  grid-template-rows: repeat(7, 9px);
  gap: 3px;
}

.github-week i,
.github-legend i {
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--border) 75%, var(--bg));
}

.github-week i.is-empty { visibility: hidden; }
.github-week i[data-level="1"],
.github-legend i[data-level="1"] { background: color-mix(in srgb, var(--accent) 28%, var(--bg)); }
.github-week i[data-level="2"],
.github-legend i[data-level="2"] { background: color-mix(in srgb, var(--accent) 50%, var(--bg)); }
.github-week i[data-level="3"],
.github-legend i[data-level="3"] { background: color-mix(in srgb, var(--accent) 72%, var(--bg)); }
.github-week i[data-level="4"],
.github-legend i[data-level="4"] { background: var(--accent); }

.github-legend {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 3px;
  color: var(--muted);
  font-size: .625rem;
}

.github-legend i { flex: none; }

.github-tooltip {
  position: absolute;
  z-index: 2;
  max-width: calc(100% - 1rem);
  width: max-content;
  padding: .35rem .5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--fg);
  color: var(--bg);
  box-shadow: var(--shadow);
  font-size: .6875rem;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  white-space: normal;
  pointer-events: none;
  transform: translate(-50%, -100%);
}

.github-tooltip::after {
  position: absolute;
  top: 100%;
  left: 50%;
  width: 7px;
  height: 7px;
  background: var(--fg);
  content: "";
  transform: translate(-50%, -50%) rotate(45deg);
}

/* --- posts --------------------------------------------------------------- */

.section-title {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .12em;
  font-weight: 700; color: var(--muted); margin-bottom: 1rem;
}

.stream { margin-top: 2.5rem; }

.post-card { padding: 1.25rem 0; border-top: 1.5px solid var(--border); }
.post-card-title { margin: 0 0 .5rem; font-size: 1.1875rem; }
.post-card-title a { text-decoration: none; }
.post-card-title a:hover { color: var(--accent); }
.post-card-excerpt, .post-card-body { margin: .25rem 0; }
.post-card-cover { margin: .75rem 0; }

.post-card-link {
  font-size: .8125rem; color: var(--muted); word-break: break-all;
}

.post-card-time {
  display: block; margin-top: .5rem;
  font-size: .75rem; font-weight: 500; color: var(--muted);
}

.post-nav { margin-bottom: 1.5rem; font-size: .875rem; font-weight: 500; }
.post-nav a { color: var(--muted); text-decoration: none; }
.post-nav a:hover { color: var(--accent); }

.post-header h1 { margin: 0 0 .5rem; font-size: 2.125rem; }
.post-header time { font-size: .8125rem; font-weight: 500; color: var(--muted); }
.post-cover { margin: 1.75rem 0; }

.more-link {
  display: inline-block; margin-top: 1.25rem;
  font-size: .875rem; font-weight: 600; color: var(--accent);
  text-decoration: none;
}

.more-link:hover { text-decoration: underline; }

.prose { font-size: 1.0625rem; }
.prose img { max-width: 100%; border-radius: var(--radius); }
.prose pre { background: var(--surface); padding: 1rem; border-radius: var(--radius); overflow-x: auto; }
.prose code { font-family: ui-monospace, Menlo, monospace; font-size: .875em; }

.prose blockquote {
  margin: 1.5rem 0; padding: .25rem 0 .25rem 1.125rem;
  border-left: 4px solid var(--accent); color: var(--muted);
}

.prose table { width: 100%; border-collapse: collapse; }
.prose th, .prose td { border: 1.5px solid var(--border); padding: .5rem; text-align: left; }
.prose th { background: var(--surface); font-weight: 700; }

/* --- forms and buttons --------------------------------------------------- */

.btn {
  display: inline-block; padding: .625rem 1.25rem; border-radius: var(--radius-sm);
  border: 1.5px solid var(--border); background: var(--bg); color: var(--fg);
  font: inherit; font-weight: 600; cursor: pointer; text-decoration: none;
}

.btn:hover { border-color: var(--accent); }
.btn-primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.btn-small { padding: .375rem .875rem; font-size: .875rem; }

input, textarea, select {
  font: inherit; color: var(--fg); background: var(--bg);
  padding: .625rem .75rem; border: 1.5px solid var(--border);
  border-radius: var(--radius-sm); width: 100%;
}

input:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
label { font-size: .8125rem; font-weight: 600; }

.stack { display: flex; flex-direction: column; gap: .5rem; text-align: left; }
.hint, .fine, .muted { color: var(--muted); font-size: .8125rem; }
.error { color: #b42318; font-size: .875rem; }
.notice { background: var(--surface); padding: .625rem .875rem; border-radius: var(--radius-sm); font-size: .875rem; }
.field-status { font-size: .8125rem; min-height: 1.25rem; }
.field-status.ok { color: #067647; }
.field-status.bad { color: #b42318; }

.username-field, .claim {
  display: flex; align-items: center; gap: .25rem;
  border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  padding: .25rem .25rem .25rem .75rem; background: var(--bg);
}

.username-field input, .claim input { border: 0; padding: .5rem .25rem; }
.username-field input:focus, .claim input:focus { outline: none; }
.prefix { color: var(--muted); white-space: nowrap; }

.divider { display: flex; align-items: center; gap: .75rem; margin: 1.5rem 0; color: var(--muted); font-size: .8125rem; }
.divider::before, .divider::after { content: ""; flex: 1; height: 1.5px; background: var(--border); }

/* --- shared page furniture ----------------------------------------------- */
/* The landing, sign-in and onboarding pages are product chrome rather than
   user-themeable content, so their styles live in marketing.css / auth.css on
   top of the --app-* tokens. What remains here is used by the public profile,
   post and document pages, which are themeable. */

.wordmark {
  display: inline-flex; align-items: baseline; gap: 3px;
  font-family: var(--font-heading);
  font-weight: 700; letter-spacing: -.03em; text-decoration: none;
}

.wordmark::after {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
}

/* The footer is the only place the product speaks on a user's own page, so it
   stays a single quiet line of links rather than a card or a banner. */
.site-footer {
  display: flex; flex-wrap: wrap;
  justify-content: center; align-items: center; gap: .625rem;
  max-width: 60rem; margin: 3rem auto 0;
  padding: 1.5rem 1.5rem 0;
  border-top: 1.5px solid var(--border);
  color: var(--muted); font-size: .8125rem; font-weight: 500;
}

.site-footer a { color: inherit; text-decoration: none; transition: color .15s ease; }
.site-footer a:hover { color: var(--accent); }

.site-footer-brand strong { color: var(--fg); font-weight: 700; }
.site-footer-brand:hover strong { color: inherit; }

/* Element+class beats the plain `.site-footer a` rule above it. */
.site-footer a.site-footer-cta { color: var(--accent); }

.site-footer .arrow { display: inline-block; transition: transform .15s ease; }
.site-footer a:hover .arrow { transform: translateX(2px); }

/* A dot rather than a pipe: lighter, and it disappears cleanly when the row
   wraps onto two lines on a phone. */
.site-footer-sep {
  width: 3px; height: 3px; border-radius: 50%;
  background: currentColor; opacity: .45;
}

/* --- ads ----------------------------------------------------------------- */

.ad-slot {
  margin: 2.5rem 0 0; padding: .875rem; text-align: center;
  border: 1.5px dashed var(--border); border-radius: var(--radius);
}

.ad-label {
  font-size: .625rem; text-transform: uppercase; letter-spacing: .12em;
  font-weight: 700; color: var(--muted);
}

.editor-loading { padding: 4rem; text-align: center; color: var(--muted); }
.empty { color: var(--muted); }

/* --- documents ----------------------------------------------------------- */
/* about / terms / privacy. These are product pages rather than user content,
   but they reuse .doc + .prose, so they only need a slightly wider measure
   and a clear heading rhythm. */

.doc { max-width: 42rem; }
.doc h1 { margin: 1.5rem 0 1rem; font-size: 2.25rem; }
.doc h2 { margin: 2.5rem 0 .75rem; font-size: 1.375rem; }

/* --- not found ----------------------------------------------------------- */
/* Reached by following a dead link, so it stays short: what happened, whether
   the handle is free, one action. */

.notfound {
  max-width: var(--width);
  margin: 0 auto;
  padding: 6rem 1.5rem 3rem;
  text-align: center;
}

.notfound-eyebrow {
  margin: 0;
  font-size: .6875rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted);
}

.notfound-title {
  margin: .875rem 0 0;
  font-family: var(--font-heading);
  font-size: 2.25rem; font-weight: 700;
  letter-spacing: -.035em; line-height: 1.08;
}

.notfound-sub {
  margin: 1rem 0 0;
  font-size: 1.0625rem; color: var(--muted);
}

/* The handle is the reason the visitor is here, so it reads as the subject of
   the sentence rather than as body copy. */
.notfound-handle { color: var(--fg); font-weight: 600; }

.notfound-action { margin-top: 2rem; }

.notfound .arrow { display: inline-block; transition: transform .15s ease; }
.notfound-action:hover .arrow { transform: translateX(2px); }

/* Pushed away from the action so the two never read as one control group. */
.notfound .site-footer { margin-top: 5rem; }

/* --- responsive ---------------------------------------------------------- */

@media (max-width: 560px) {
  .profile, .post, .archive, .doc { padding: 2rem 1rem 3rem; }
  .profile-name { font-size: 1.625rem; }
  .post-header h1 { font-size: 1.75rem; }
  .doc h1 { font-size: 1.75rem; }
  .notfound { padding: 4rem 1.25rem 2.5rem; }
  .notfound-title { font-size: 1.875rem; }
  .notfound-sub { font-size: 1rem; }
}

/* Desktop. Everything here is additive and gated behind min-width, so the
   phone layout -- and the editor's phone preview, which is the only place the
   block sizes are visible while editing -- is untouched.

   Only .profile widens. .post, .archive and .doc are prose and keep --width so
   their reading measure stays comfortable. */
@media (min-width: 45rem) {
  .profile { max-width: 45rem; padding: 3.5rem 2rem 5rem; }

  /* A 1x1 tile is ~340px wide here and ~425px at the next step, so it needs
     real height or it reads as a stretched button rather than a tile. */
  .blocks {
    --block-row: 7rem;
    --block-gap: .875rem;
    gap: var(--block-gap);
    margin: 2.5rem 0;
    /* Fills the hole an odd run of small blocks leaves in front of a wide one.
       Deliberately not applied on mobile, where it would reshuffle the current
       layout and diverge from the editor canvas. */
    grid-auto-flow: dense;
  }

  /* Four columns make a 1x1 ~154px wide, so the row grows to match and the
     tile reads square. Two columns keep the 7rem above, which is what every
     existing page was laid out against. */
  .blocks.is-cols-4 {
    --block-row: 9.5rem;
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  /* Spans stop being clamped once there is room for them. Python has already
     resolved the stored quarter-columns against the active column count, so
     is-w{n} is literally n columns here. */
  .blocks.is-cols-4 > .block.is-w2 { grid-column: span 2; }
  .blocks.is-cols-4 > .block.is-w3 { grid-column: span 3; }
  .blocks.is-cols-4 > .block.is-w4 { grid-column: span 4; }
  .blocks.is-cols-4 > .block.is-h3 {
    grid-row: span 3;
    min-height: calc(3 * var(--block-row) + 2 * var(--block-gap));
  }
  .blocks.is-cols-4 > .block.is-h4 {
    grid-row: span 4;
    min-height: calc(4 * var(--block-row) + 3 * var(--block-gap));
  }
  .blocks.is-cols-4 > .block-link.is-h3 {
    height: calc(3 * var(--block-row) + 2 * var(--block-gap));
  }
  .blocks.is-cols-4 > .block-link.is-h4 {
    height: calc(4 * var(--block-row) + 3 * var(--block-gap));
  }

  /* The compact two-column row cannot fit a useful top image plus two lines of
     metadata. Keep the title and give the media the recovered height; taller
     cards continue to show the description. */
  .blocks.is-cols-2 > .block-layout-image_top.has-image.is-h1 .block-desc {
    display: none;
  }

  .block { min-height: var(--block-row); }
  .block.is-h2, .block.is-h3, .block.is-h4 {
    min-height: calc(2 * var(--block-row) + var(--block-gap));
  }
  /* Re-stated because the wider .block rule above now comes later in the
     cascade at equal specificity. A heading is a label, not a tile. */
  .block-heading { min-height: 0; }

  /* The four-column row stays square for image tiles and taller presets, but a
     one-row link is content, not a square canvas. Match the compact editor card
     instead of inflating it as the profile widens. */
  .blocks.is-cols-4
    > .block-link.is-h1:not(.block-layout-image_only) {
    height: auto;
    min-height: 7rem;
  }

  /* Side media fills the padded height when the card is wide enough to leave a
     readable text column. One-row cards use a narrower panel; multi-row cards
     can devote up to 42% of their width to the image. */
  .blocks.is-cols-2
    > :is(.block-layout-image_left, .block-layout-image_right).has-image:is(.is-h1, .is-h2)
    > .block-icon,
  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image:not(.is-w1):is(.is-h1, .is-h2)
    > .block-icon,
  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image:is(.is-w3, .is-w4):is(.is-h3, .is-h4)
    > .block-icon {
    align-self: stretch;
    width: min(42%, 20rem);
    height: auto;
    aspect-ratio: auto;
  }

  /* Banner-height cards use a stable 40/60 media-to-copy split. The image still
     fills the padded card height, avoiding empty space above and below it. */
  .blocks.is-cols-2
    > :is(.block-layout-image_left, .block-layout-image_right).has-image.is-h1
    > .block-icon,
  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image:not(.is-w1).is-h1
    > .block-icon {
    width: 40%;
  }

  /* A card that is taller than it is wide needs a vertical composition. Stack
     side layouts and let their image occupy the available space above the text
     instead of producing a narrow, heavily cropped side strip. */
  .blocks.is-cols-2
    > :is(.block-layout-image_left, .block-layout-image_right).has-image:is(.is-h3, .is-h4),
  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image.is-w2:is(.is-h3, .is-h4) {
    flex-direction: column;
    align-items: stretch;
    text-align: left;
  }

  .blocks.is-cols-2
    > :is(.block-layout-image_left, .block-layout-image_right).has-image:is(.is-h3, .is-h4)
    > .block-icon,
  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image.is-w2:is(.is-h3, .is-h4)
    > .block-icon {
    align-self: stretch;
    flex: 1;
    width: 100%;
    height: auto;
    min-height: 0;
  }

  .blocks.is-cols-2
    > :is(.block-layout-image_left, .block-layout-image_right).has-image:is(.is-h3, .is-h4)
    > .block-body,
  .blocks.is-cols-4
    > :is(.block-layout-image_left, .block-layout-image_right).has-image.is-w2:is(.is-h3, .is-h4)
    > .block-body {
    flex: none;
  }

  /* Images get a fixed ratio per size on desktop rather than their intrinsic
     one. Left to grow naturally a single 4:3 photo drags its whole row taller,
     which then stretches every neighbour and any block spanning that row -- a
     tall embed beside one measured 454px. Fixed ratios keep the grid a grid,
     and cover crops far less than squashing a photo into a 3.5:1 tile would. */
  .block-image img {
    flex: none;
    height: auto;
    aspect-ratio: 16 / 9;
  }

  /* Tall is the one size whose whole point is vertical room. A block that is
     both tall and full width is wide enough to keep a landscape crop. */
  .block-image.is-h2.is-w1 img,
  .block-image.is-h4.is-w1 img,
  .block-image.is-h4.is-w2 img { aspect-ratio: 3 / 4; }

  .now { margin: 2.5rem 0; }
  .stream { margin-top: 3rem; }

  /* The container is now wider than a comfortable line, so the text inside it
     is capped while the dividers keep spanning the full width. */
  .profile-bio { max-width: 34rem; margin-left: auto; margin-right: auto; }
  .post-card-excerpt, .post-card-body { max-width: 62ch; }
}

@media (min-width: 64rem) {
  .profile { max-width: 54rem; padding: 4rem 2rem 5rem; }

  /* The container grows to 800px of content here, so a four-column cell is
     ~190px and the row follows it to stay square. */
  .blocks.is-cols-4 { --block-row: 11.75rem; }
}

/* --- profile layouts ----------------------------------------------------- */
/* Layout is independent from theme: these rules only compose the shared
   profile regions and continue to use the same palette and shape tokens. */

.profile-tail { min-width: 0; }

/* The alternate layouts keep a hint of their composition on phones rather
   than collapsing into four visually identical pages. */
.profile-layout-sidebar .profile-header {
  padding: 1.5rem;
  border-radius: var(--radius);
  background: var(--sidebar);
}

.profile-layout-canvas .profile-header,
.profile-layout-editorial .profile-header { text-align: left; }

.profile-layout-canvas .avatar-wrap,
.profile-layout-editorial .avatar-wrap { margin-left: 0; }

.profile-layout-canvas .socials,
.profile-layout-editorial .socials { justify-content: flex-start; }

.profile-layout-editorial .profile-name {
  font-size: clamp(2rem, 10vw, 3rem);
  letter-spacing: -.045em;
}

@media (min-width: 60rem) {
  /* Sidebar: an immersive identity rail inspired by portfolio dashboards. The
     content remains ordinary document flow in the right column. */
  .profile-layout-sidebar {
    position: relative;
    max-width: none;
    min-height: 100vh;
    padding: 0;
    background:
      linear-gradient(
        to right,
        var(--sidebar) 0 max(18rem, 34vw),
        var(--bg) max(18rem, 34vw) 100%
      );
  }

  .profile-layout-sidebar .profile-shell {
    display: grid;
    grid-template-columns: minmax(18rem, 34vw) minmax(0, 1fr);
    align-items: start;
  }

  .profile-layout-sidebar .profile-header {
    position: sticky;
    top: 0;
    display: flex;
    min-height: 100vh;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    padding: clamp(3rem, 7vw, 7rem);
    border-radius: 0;
    text-align: left;
  }

  .profile-layout-sidebar .avatar-wrap { margin: 0; }
  .profile-layout-sidebar .profile-name {
    max-width: 10ch;
    margin-top: 2rem;
    font-size: clamp(2.5rem, 4vw, 4.5rem);
    letter-spacing: -.055em;
  }
  .profile-layout-sidebar .profile-bio { max-width: 32rem; margin-left: 0; }
  .profile-layout-sidebar .socials { justify-content: flex-start; }

  .profile-layout-sidebar .profile-body {
    width: min(100%, 64rem);
    padding: 2rem clamp(2rem, 5vw, 5rem) 5rem;
  }

  .profile-layout-sidebar .profile-tail {
    width: min(calc(100% - max(18rem, 34vw)), 64rem);
    margin-left: max(18rem, 34vw);
    padding: 0 clamp(2rem, 5vw, 5rem) 3rem;
  }

  /* Canvas: identity stays restrained while the bento content receives most of
     the page. Unlike Sidebar, both columns sit on the same background. */
  .profile-layout-canvas {
    max-width: 82rem;
    padding: 4rem 2.5rem 5rem;
  }

  .profile-layout-canvas .profile-shell {
    display: grid;
    grid-template-columns: minmax(15rem, 19rem) minmax(0, 1fr);
    align-items: start;
    gap: clamp(3rem, 7vw, 7rem);
  }

  .profile-layout-canvas .profile-header {
    position: sticky;
    top: 4rem;
  }

  .profile-layout-canvas .profile-name {
    margin-top: 1.75rem;
    font-size: clamp(2.25rem, 3.5vw, 3.75rem);
    letter-spacing: -.055em;
  }

  .profile-layout-canvas .profile-bio {
    max-width: 28rem;
    margin-left: 0;
    font-size: 1.0625rem;
  }

  .profile-layout-canvas .profile-body > :first-child { margin-top: 0; }
  .profile-layout-canvas .profile-tail {
    width: calc(100% - min(26rem, 30vw));
    margin-left: auto;
  }

  /* Editorial: the introduction behaves like a masthead, then links and
     writing become distinct reading columns. */
  .profile-layout-editorial {
    max-width: 74rem;
    padding: 4rem 2.5rem 5rem;
  }

  .profile-layout-editorial .profile-header {
    display: grid;
    grid-template-columns: 7.5rem minmax(0, 1fr);
    column-gap: 2rem;
    padding-bottom: 3rem;
    border-bottom: 1.5px solid var(--border);
  }

  .profile-layout-editorial .avatar-wrap {
    grid-column: 1;
    grid-row: 1 / span 5;
    width: 120px;
    height: 120px;
  }

  .profile-layout-editorial .avatar-initial {
    font-size: 2.75rem;
  }

  .profile-layout-editorial .avatar {
    width: 120px;
    height: 120px;
  }

  .profile-layout-editorial .profile-name,
  .profile-layout-editorial .profile-bio,
  .profile-layout-editorial .profile-location,
  .profile-layout-editorial .socials { grid-column: 2; }

  .profile-layout-editorial .profile-name {
    margin: -.2rem 0 .5rem;
    font-size: clamp(3.25rem, 7vw, 6rem);
  }

  .profile-layout-editorial .profile-bio {
    max-width: 48rem;
    margin-left: 0;
    font-size: 1.125rem;
  }

  .profile-layout-editorial .profile-body.has-blocks.has-posts {
    display: grid;
    grid-template-columns: minmax(0, 1.65fr) minmax(17rem, .75fr);
    grid-template-areas:
      "now now"
      "blocks stream";
    column-gap: clamp(2.5rem, 6vw, 5rem);
    align-items: start;
  }

  .profile-layout-editorial .now { grid-area: now; }
  .profile-layout-editorial .blocks { grid-area: blocks; }
  .profile-layout-editorial .stream { grid-area: stream; }
  .profile-layout-editorial .blocks,
  .profile-layout-editorial .stream { margin-top: 0; }
  .profile-layout-editorial .stream .section-title { margin-top: 0; }
}

@media (min-width: 60rem) and (max-width: 74rem) {
  .profile-layout-editorial .profile-body.has-blocks.has-posts {
    grid-template-columns: minmax(0, 1.35fr) minmax(15rem, .75fr);
    column-gap: 2.5rem;
  }
}
