/* Karli's creator site — phone first. Her own palette (sage, terracotta, warm
   off-white) comes from the theme settings and lands as custom properties in
   the document head, so changing a color is a database edit, not a deploy. */

:root {
  /* How wide every coupon stub is, and therefore where the perforation falls.
     One number for the whole page — see the ".chip" block for why. Measured in
     a real 390px viewport: it is the width at which her longest code,
     LOVEKARLIYARBER20, still sits on one line at the smallest size band, with
     3px to spare. */
  --chip-w: 136px;
}

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--page-bg);
  color: var(--text);
  font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.45;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
}

.page {
  max-width: 520px;
  margin: 0 auto;
  padding: 28px 16px calc(40px + env(safe-area-inset-bottom));
}

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

.profile {
  text-align: center;
  margin-bottom: 18px;
}

.profile__avatar {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  margin: 0 auto 12px;
  box-shadow: 0 0 0 3px var(--page-bg), 0 0 0 4.5px var(--accent);
}

.profile__name {
  position: relative;
  display: inline-block;
  margin: 0;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* The hand-drawn underline is the one flourish on the page. It sits under her
   name only, in the terracotta she already chose. */
.squiggle {
  position: absolute;
  left: 0;
  bottom: -7px;
  width: 100%;
  height: 7px;
  color: var(--accent);
  opacity: 0.85;
}

.profile__bio {
  margin: 16px auto 0;
  max-width: 34ch;
  font-size: 14px;
  color: #4a4a4a;
}

.profile__socials {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 14px;
}

.social {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  color: var(--accent);
  border: 1.5px solid currentColor;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.social svg {
  width: 19px;
  height: 19px;
  fill: currentColor;
}

.social:active {
  background: var(--accent);
  color: #fff;
}

/* ---------- affiliate disclosure ----------
   The FTC wants this visible alongside the links rather than parked in a
   footer, so it sits above the first button and is never shrunk into fine
   print. */

.disclosure {
  margin: 0 0 24px;
  padding: 9px 14px;
  border-radius: 12px;
  background: rgba(228, 141, 92, 0.14); /* fallback for browsers without color-mix */
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: #4a4a4a;
  font-size: 12.5px;
  line-height: 1.4;
  text-align: center;
}

/* ---------- sections ---------- */

.section {
  margin-bottom: 26px;
}

.section__title {
  margin: 0 0 10px 2px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #79796f;
}

/* The line under the starred block's heading, saying how it is ordered. Pulled
   up against the heading so the two read as one label rather than as a heading
   with a paragraph under it — it is a caption, and it costs 16px of the first
   screen, which is the most expensive space on the page. */
.section__note {
  margin: -7px 0 11px 2px;
  color: #8b8b80;
  font-size: 12px;
  font-style: italic;
  line-height: 1.35;
}

/* ---------- the button ----------
   One shape for every brand: name, the discount in smaller text, and a
   coupon stub on the right carrying the code. Brands with no typed code keep
   the same stub and say what actually happens instead. */

.card {
  position: relative;
  display: flex;
  align-items: stretch;
  min-height: 64px;
  margin-bottom: 12px;
  border-radius: 14px;
  overflow: hidden;
  background: var(--button-bg);
  color: var(--button-text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}

.card__link {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding: 12px 8px 12px 15px;
  color: inherit;
  text-decoration: none;
}

/* Stretching the anchor over the whole card keeps the tap target the entire
   button while leaving the copy control a real <button> on top of it. */
.card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.card__brand {
  font-size: 16px;
  font-weight: 700;
  line-height: 1.2;
  /* With a photo on the left the name has ~170px at 390px, which a long brand
     can outrun. Clipping keeps every card the same height either way. */
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.card--featured .card__brand {
  font-size: 17px;
}

/* One line, never two: every button is the same height, which is the whole
   point of standardizing the look. A blurb too long for the space is clipped
   with an ellipsis rather than being allowed to grow the card. */
.card__meta {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
  min-height: 19px;
  font-size: 12.5px;
  line-height: 1.3;
}

.card__discount {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.18);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0.01em;
}

.card__blurb {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  opacity: 0.78;
}

.card:active {
  filter: brightness(0.94);
}

/* ---------- the photo corner ----------
   The only way into a brand's own page. It sits ABOVE .card__link::after (which
   is stretched over the whole card) for the same reason the coupon stub does:
   without the z-index the stretched anchor swallows the tap and the photo is
   decorative. */

.card__shot {
  position: relative;
  z-index: 1;
  flex: 0 0 64px;
  align-self: stretch;
  display: block;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.08);
  border-right: 1px solid rgba(255, 255, 255, 0.2);
}

.card__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card__shot:active img {
  filter: brightness(0.85);
}

/* A count over the corner of the photo is what says "there is more behind
   this" — without it the thumbnail reads as decoration and nobody taps it. */
.card__shotcount {
  position: absolute;
  right: 3px;
  bottom: 3px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

/* A video cover says "there is something to watch here" with a play triangle
   instead of a count. It is a stronger reason to tap than a number, and a bare
   "1" on a single clip would say less than nothing. */
.card__shotplay {
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
}

.card__shotplay svg {
  display: block;
  width: 100%;
  height: 100%;
}

.card--shot .card__link {
  padding-left: 12px;
}

/* ---------- the coupon stub ----------

   THE PERFORATION IS IN THE SAME PLACE ON EVERY BUTTON, and that is the whole
   point of this block. Karli, 2026-08-06: "the little 'ticket' type design is
   cool and helps separate it all out between the link and the code, but it
   looks a little messy with them all being perforated in a different spot."

   She was right, and it was measurable: with the stub sized to its own content
   the tear line landed anywhere from 235px to 283px down a 390px page, because
   KARLI is five characters and LOVEKARLIYARBER20 is seventeen. A fixed width
   makes the tear line one straight edge down the whole page, which is what a
   sheet of real tickets looks like.

   The width is one number, --chip-w, and it is sized to the widest thing a stub
   ever holds: her longest code at the smallest size band. Do NOT go back to
   sizing this to content, and if you change the bands in valueSizeClass()
   (src/render.ts) re-measure this number in a 390px browser — the two are one
   mechanism split across two files, and there is a test on the pairing. */

.chip {
  position: relative;
  flex: 0 0 var(--chip-w);
  width: var(--chip-w);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 8px 10px;
  border: 0;
  border-left: 1.5px dashed rgba(255, 255, 255, 0.45);
  background: rgba(255, 255, 255, 0.12);
  color: inherit;
  font-family: inherit;
  text-align: center;
  z-index: 1;
  -webkit-user-select: none;
  user-select: none;
}

/* The two notches punched out of the perforation, so the stub reads as a
   coupon at a glance rather than as a second button. */
.chip::before,
.chip::after {
  content: "";
  position: absolute;
  left: -6px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--page-bg);
}

.chip::before {
  top: -6px;
}

.chip::after {
  bottom: -6px;
}

.chip__value {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  overflow-wrap: anywhere;
  line-height: 1.15;
}

.chip__value--sm {
  font-size: 11.5px;
  letter-spacing: 0.02em;
}

.chip__value--xs {
  font-size: 10.5px;
  letter-spacing: 0.01em;
}

/* Longer than anything Karli uses today; the admin caps a code at 20 characters
   (CODE_MAX_CHARS) because past that even this band wraps. */
.chip__value--xxs {
  font-size: 9px;
  letter-spacing: 0;
}

.chip__label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  opacity: 0.72;
}

.chip--static {
  cursor: pointer;
}

.chip--static:active {
  background: rgba(255, 255, 255, 0.24);
}

.chip--auto .chip__value,
.chip--issued .chip__value {
  letter-spacing: 0.01em;
  text-transform: none;
  font-size: 13px;
  opacity: 0.95;
}

.chip.is-copied {
  background: var(--accent);
}

/* ---------- a brand's own page ----------
   Her photos of one brand, the discount, and the button. The page a visitor
   lands on when they tap the photo corner, and the thing no off-the-shelf
   link-in-bio platform does well. */

.backlink {
  display: inline-block;
  margin-bottom: 14px;
  color: #6a6a62;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}

.backlink::before {
  content: "← ";
}

.backlink:active {
  color: var(--accent);
}

.shot {
  margin: 0 0 18px;
}

.shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 14px;
  background: rgba(0, 0, 0, 0.05);
}

/* A clip gets the same silhouette as a photo — same width, same corner radius,
   same spacing — so a gallery of both does not read as two different things. */
.shot--video {
  position: relative;
}

.shot__video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 14px;
  /* Black rather than the photo's light gray: it is what letterboxing looks
     like inside the built-in player, so the box and the bars match. */
  background: #000;
}

/* How long the clip is, printed on it. Nothing is preloaded, so the player's
   own controls cannot say until it has already started — and "how long is
   this" is the question somebody asks before tapping, not after. */
.shot__length {
  position: absolute;
  right: 10px;
  top: 10px;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  pointer-events: none;
}

.shot__caption {
  margin: 8px 2px 0;
  color: #5c5c55;
  font-size: 13px;
  line-height: 1.45;
}

.brandhead {
  margin-bottom: 14px;
}

.brandhead__name {
  margin: 0;
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
}

.brandhead__discount {
  display: inline-block;
  margin: 8px 0 0;
  padding: 3px 11px;
  border-radius: 999px;
  background: rgba(228, 141, 92, 0.18);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: #8a4a1e;
  font-size: 13px;
  font-weight: 700;
}

.story {
  margin-bottom: 20px;
  font-size: 15px;
  line-height: 1.6;
  color: #3a3a35;
}

.story p {
  margin: 0 0 12px;
}

.story p:last-child {
  margin-bottom: 0;
}

/* The buy button is the same shape as the button on her links page, so it is
   recognizably the same control rather than a new thing to work out. */
.card--buy {
  min-height: 72px;
  margin: 0 0 20px;
}

.gallery {
  margin-bottom: 20px;
}

.footer a {
  color: inherit;
  font-weight: 600;
}

.footer .profile__socials {
  margin-top: 12px;
}

/* ---------- the media kit at /work ---------- */

.profile--work {
  margin-bottom: 4px;
}

.worksection {
  margin-top: 26px;
}

.worksection__title {
  margin: 0 0 10px;
  color: var(--button-bg);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.worksection p {
  margin: 0 0 10px;
  font-size: 15px;
  line-height: 1.6;
}

.worksection p:last-child {
  margin-bottom: 0;
}

/* The opening paragraph is the first thing a brand reads, so it is set a size
   up from the rest of the page rather than being another block of body copy. */
.pitch p {
  font-size: 16.5px;
  line-height: 1.62;
}

/* Two across at phone width. A third column would put a four-digit follower
   count on two lines, which reads as a smaller number than it is. */
.numbers {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

.numbers__tile {
  padding: 14px 12px;
  border: 1px solid rgba(100, 125, 94, 0.25);
  border-radius: 14px;
  background: #fff;
  text-align: center;
}

.numbers__value {
  display: block;
  color: var(--button-bg);
  font-size: 26px;
  font-weight: 700;
  line-height: 1.1;
}

.numbers__label {
  display: block;
  margin-top: 4px;
  color: #6f6f66;
  font-size: 12px;
  line-height: 1.35;
}

/* A follower count with no date on it is a claim rather than a fact — so this
   line is set dark enough to actually read (4.9:1 on the page background, where
   the footer's lighter gray would have been 2.9:1 and effectively decorative). */
.numbers__asof {
  margin-top: 10px;
  color: #67675e;
  font-size: 12px;
}

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

.services li {
  position: relative;
  padding: 7px 0 7px 20px;
  font-size: 15px;
  line-height: 1.5;
}

.services li::before {
  content: "";
  position: absolute;
  top: 15px;
  left: 2px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
}

.collab {
  margin-bottom: 12px;
  padding: 14px 16px;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.collab:last-child {
  margin-bottom: 0;
}

.collab__brand {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 0;
  font-size: 17px;
  font-weight: 700;
}

.collab__when {
  flex: none;
  color: #8d8d85;
  font-size: 12px;
  font-weight: 600;
}

.collab__work {
  margin: 5px 0 0;
  color: #55554e;
  font-size: 14px;
  line-height: 1.45;
}

/* The line a brand is actually scanning for, so it is the loudest thing in the
   card after the name — a result is the whole reason this page exists. */
.collab__outcome {
  display: inline-block;
  margin: 9px 0 0;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(228, 141, 92, 0.16);
  color: #8a4a1e;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.35;
}

.collab__quote {
  margin: 11px 0 0;
  padding-left: 12px;
  border-left: 3px solid rgba(100, 125, 94, 0.35);
  color: #55554e;
  font-size: 14px;
  font-style: italic;
  line-height: 1.5;
}

/* The button down to one brand's examples. Full width and set apart from the
   card's text, because it is the only tappable thing in a block of prose and a
   link that looks like prose is a link nobody taps. */
.collab__examples {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid rgba(100, 125, 94, 0.3);
  border-radius: 11px;
  color: var(--button-bg);
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none;
}

.collab__examples:active {
  background: var(--button-bg);
  color: var(--button-text);
}

.collab__examples-count {
  flex: none;
  min-width: 22px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(100, 125, 94, 0.14);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* ---------- work samples ---------- */

/* The heading over one format group. Smaller than a section title and in the
   body color, so a brand reads "My work" as the section and these as the kinds
   of thing inside it rather than as five separate sections. */
.samples__format {
  margin: 16px 0 8px;
  font-size: 14px;
  font-weight: 700;
}

.samples__format:first-of-type {
  margin-top: 0;
}

/* Two across. A third column at 390px puts a result like "61k views" onto two
   lines over a 105px picture, which is small enough that the picture stops
   being a preview of anything. */
.samples {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-bottom: 14px;
}

.sample {
  margin: 0;
}

/* Every tile is the same square whatever is inside it, so a row of a reel, a
   photo and a bare post link still reads as one grid. */
.sample__frame {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 13px;
  background: #eeeee6;
  color: inherit;
  text-decoration: none;
}

.sample__img,
.sample__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Black rather than the photo's light gray: it is what letterboxing looks like
   inside the built-in player, so a portrait clip in a square box matches. */
.sample__video {
  background: #000;
  object-fit: contain;
}

/* A post link with no picture on it. It used to be a dashed outline around the
   platform's name, which is what Karli wrote in about on 2026-08-06 — it reads
   as a thumbnail that failed to load rather than as something anybody designed.
   Now it is a filled card in her sage, carrying the kind of thing it is: her
   own suggestion, and the one shape of tile that cannot look broken. */
.typecard {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 100%;
  padding: 12px 10px;
  /* Two stops of the button green rather than a flat fill, so a row of these
     next to real photographs reads as designed rather than as a placeholder. */
  background: linear-gradient(155deg, #7b9273 0%, var(--button-bg) 58%, #52684c 100%);
  color: #fff;
  text-align: center;
}

/* The mark: an outlined picture frame for a still, a circle with a play
   triangle in it for anything that moves. Two shapes rather than one, because
   the whole job of this tile is to say what kind of thing it is — and they
   never appear side by side anyway, since the format is also the heading.
   Drawn in CSS for the same reason the play badge is: an icon file would be
   another request on a page loading over cell data. */
.typecard__mark {
  position: relative;
  width: 34px;
  height: 26px;
  flex: none;
  border: 2px solid rgba(255, 255, 255, 0.78);
  border-radius: 5px;
}

/* A hill standing on the bottom edge of the frame — the picture-in-a-frame
   glyph everything else uses. Without something inside it the mark reads as an
   empty box, which is the exact impression this whole card exists to avoid. */
.typecard__mark::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 50%;
  margin-left: -10px;
  border-style: solid;
  border-width: 0 10px 10px 10px;
  border-color: transparent transparent rgba(255, 255, 255, 0.6) transparent;
}

.typecard__mark--play {
  width: 30px;
  height: 30px;
  border-radius: 50%;
}

.typecard__mark--play::after {
  right: auto;
  bottom: auto;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  margin: -6px 0 0 -3px;
  border-radius: 0;
  background: none;
  border-style: solid;
  border-width: 6px 0 6px 10px;
  border-color: transparent transparent transparent rgba(255, 255, 255, 0.92);
}

.typecard__type {
  font-size: 13px;
  font-weight: 700;
  line-height: 1.25;
  /* "Talking to camera" is the longest one and wants two lines at 165px wide,
     balanced rather than breaking after the first word. */
  text-wrap: balance;
}

.typecard__where {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.82);
}

/* On a card there is no photograph for the result to sit on, so it stops being
   a floating pill and becomes a band across the foot. A dark pill hovering over
   a flat green square reads as something that came loose. */
.sample__frame--card {
  display: flex;
  flex-direction: column;
}

.sample__frame--card .typecard {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
}

/* A solid color, not a translucent black: this band sits BELOW the card rather
   than on top of it, so a translucent fill mixes with the page's off-white and
   comes out a gray strip hanging off the bottom of a green tile. This is the
   dark end of the card's own gradient. */
.sample__frame--card .sample__metric {
  position: static;
  flex: none;
  max-width: none;
  padding: 6px 9px;
  border-radius: 0;
  background: #47593f;
  text-align: center;
}

/* The play triangle on a still that leads to a video post. Drawn in CSS rather
   than shipped as an image: it is two rules, and an icon file would be another
   request on a page loading over cell data.

   A plain white triangle with a shadow rather than a triangle in a dark disc,
   because Instagram burns its own play glyph into the preview picture it hands
   out and that glyph is exactly this shape. Ours only appears on the tiles
   theirs does not, so the two have to look like the same mark or a row of four
   tiles carries two different play buttons — the "messy, all in a different
   spot" note Karli already filed once about the discount buttons. The shadow is
   what keeps it visible over a bright frame. */
.sample__play {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  margin: -15px 0 0 -11px;
  border-style: solid;
  border-width: 15px 0 15px 25px;
  border-color: transparent transparent transparent #fff;
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.55));
  pointer-events: none;
}

.sample__length {
  position: absolute;
  right: 8px;
  top: 8px;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* The number, on the picture. Every media-kit guide says the same thing: a
   brand should not have to take her word for how a post did, and the place they
   believe it is on the post itself. */
.sample__metric {
  position: absolute;
  left: 8px;
  bottom: 8px;
  max-width: calc(100% - 16px);
  padding: 3px 9px;
  /* Not a 999px stadium: this pill is allowed to run to two lines, and a
     stadium radius on two lines reads as a squashed oval. */
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.35;
  /* Wraps rather than truncating. A clipped number is the same defect Karli
     already filed against the link buttons ("Pump bag for workin…") — and the
     half she loses here is the half that sells, since "380 codes used" always
     falls after the view count. */
  text-wrap: balance;
  pointer-events: none;
}

/* Where tapping goes, on its own line under the caption. It used to sit in the
   picture's bottom corner and ran straight through a metric as long as
   "22k views · 380 codes used" — at 165px there is no room for both. */
.sample__where {
  display: block;
  margin: 3px 2px 0;
  color: var(--button-bg);
  font-size: 11.5px;
  font-weight: 700;
}

.sample__body {
  display: block;
  color: inherit;
  text-decoration: none;
}

.sample__body:active .sample__where {
  color: var(--accent);
}

.sample__title {
  display: block;
  margin: 6px 2px 0;
  color: #5c5c55;
  font-size: 12.5px;
  line-height: 1.4;
}

.examples__meta {
  margin: 4px 0 0;
  color: #7a7a70;
  font-size: 13.5px;
}

.contact .cta {
  margin-top: 0;
}

/* The same block at the foot of the links page, where there is no section
   heading above it, so the button keeps its own top margin. */
.contactblock {
  margin: 0;
}

.contact__address {
  margin-top: 8px;
  color: #8d8d85;
  font-size: 13px;
  text-align: center;
}

/* ---------- work with me, footer, toast ---------- */

.cta {
  display: block;
  margin-top: 28px;
  padding: 14px;
  border: 1.5px solid var(--button-bg);
  border-radius: 14px;
  color: var(--button-bg);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: center;
  text-decoration: none;
}

.cta:active {
  background: var(--button-bg);
  color: var(--button-text);
}

.footer {
  margin-top: 24px;
  text-align: center;
  color: #8d8d85;
  font-size: 12px;
}

.footer p {
  margin: 0;
}

/* Laid out between left:0 and right:0 rather than nudged from left:50%: a
   fixed element anchored at 50% only has half the viewport to lay out in, so a
   long code wrapped the toast onto two lines. */
.toast {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(22px + env(safe-area-inset-bottom));
  width: fit-content;
  max-width: calc(100% - 32px);
  margin: 0 auto;
  transform: translateY(16px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 10px 20px;
  border-radius: 999px;
  background: #2f3a2c;
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.toast.is-shown {
  opacity: 1;
  transform: translateY(0);
}

a:focus-visible,
button:focus-visible {
  outline: 2.5px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
  }
}
