/* Everything on karliyarber.com's own pages, including the tab ribbon at the
   bottom of this file. editor.css loads after this one. Her palette is copied
   from her links app so the two feel like one site.

   Karli asked on 2026-08-07 for the whole site to be "consistent but
   memorable", after finding buttons of three different sizes on one page and a
   different sign-off at the bottom of each. So there are now exactly four
   numbers that decide how anything on this site looks, declared once below and
   used everywhere:

     --control-h     every tappable thing is this tall. Buttons, social
                     links, the lot — that is what makes a row of them read as
                     one set rather than three sizes.
     --radius-lg     cards.
     --radius-md     controls.
     --gap           the space between two things sitting side by side.

   If you add something to this site, reach for those rather than a new value.
   A one-off 14px is how the last set of mismatched buttons happened.

   ONE COLOR DRAWS THE SITE. Karli, 2026-08-10: "I'm also not loving the mostly
   orange theme with a random green button or something thrown in. Do you think
   you could pick a theme and go with it?" The first pass at that kept terracotta
   for her hand-drawn mark — the squiggle under a heading, the ring around her
   photo — and she came straight back the same day: "The line under my name is
   still orange and so is the circle around my photo." She was right, and the
   lesson is that "green for controls, terracotta for marks" is a rule an
   engineer can see and a person cannot. So there is one:

     --control   sage green. EVERY line this site draws: both kinds of button,
                 every tab, the social links, the chevron on a dropdown, the
                 figures inside those, the hand-drawn squiggle under a heading,
                 the ring around her photo, the stroke under her address.
     --mark      terracotta, and now ONLY where something has to interrupt her:
                 the focus ring (green on a green button would be invisible) and
                 the editor's "that didn't save" banner. It is the alarm color,
                 not a second brand color — which is why it may not decorate
                 anything.

   Green leads because /links leads with it: her links app is a page of solid
   sage cards and we are not allowed to edit it, so anything else would have made
   one of the four tabs look like somebody else's. The color on this site is the
   wildflowers, which is the whole reason they are there. `pages.test.ts` reads
   this file and fails if a new rule reaches for --mark outside those two, which
   is the only thing that would catch the drift coming back.

   THE CLOTH AND THE FLOWERS. Karli, 2026-08-10, once the palette was settled:
   "Could we go for a fun and lively design? I really love green and white
   gingham, colorful wildflowers, etc. ... I want it to be colorful and fun and
   show my personality, but still professional." So the page itself is a green
   and white gingham cloth and a row of wildflowers grows under the hero.

   The two rules that keep it professional rather than busy, and both are the
   reason it works at all:

     * EVERY WORD SITS ON WHITE. The gingham is only ever the backdrop; the
       cards lift off it on a soft shadow. Body text on a check pattern is the
       way this look goes wrong, so if you add a block of prose, put it in
       `.section` like every other block.
     * THE BLOOMS ARE DECORATION, NOT SIGNAL. --bloom-* colors belong to the
       wildflowers and nothing else. A button, a figure or a heading reaching
       for one puts this site back to having no rule about color, which is the
       exact thing she reported. Green is still what you tap. */

/* ---- Open Sans, served from this site rather than from Google ----

   A <link> to fonts.googleapis.com reports every one of Karli's visitors to
   Google — their IP address, their browser, and which of her pages they are on
   — before a word of hers is drawn. Nothing about her site needs that, so the
   two files these rules point at live in site/fonts/open-sans-v44/ and are
   served from her own domain. See the README beside them.

   ONE file per subset, not one per weight. Open Sans is a variable font, so the
   whole 300-800 weight axis is in each file: Google's own stylesheet points
   400, 600 and 700 at exactly these two URLs. Declaring the range rather than a
   single weight is what lets a browser use it for all three.

   The unicode-range lines are Google's, copied verbatim, and they are what make
   the second file free: a browser fetches latin-ext ONLY if the page actually
   contains a character from it — an accented brand name — so an American
   English page downloads one 48 KB file and stops.

   font-display: swap because she and her audience are on cellular. Her words
   appear immediately in the system font and reflow when the file lands, rather
   than the page sitting blank while it downloads. */
@font-face {
  font-family: "Open Sans";
  font-style: normal;
  font-weight: 300 800;
  font-stretch: 100%;
  font-display: swap;
  src: url("/_home/fonts/open-sans-v44/latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Open Sans";
  font-style: normal;
  font-weight: 300 800;
  font-stretch: 100%;
  font-display: swap;
  src: url("/_home/fonts/open-sans-v44/latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* The same two hues her links app declares as --button-bg and --accent in its
     own document head — named here for the job they do rather than for where
     they happened to land first, because "accent" is what invited the drift. */
  --control: #647d5e;
  --control-text: #ffffff;
  --mark: #e48d5c;

  --page-bg: #f4f4f4;
  --text: #000000;
  --muted: rgba(0, 0, 0, 0.58);
  --card: #ffffff;
  --line: rgba(0, 0, 0, 0.09);

  /* ---- the gingham cloth ----
     Real gingham is two translucent bands crossing, not a checkerboard: where
     they overlap you get the dark square, where one runs alone you get the pale
     one, and where neither does you get the cloth. That is why this is two
     gradients of the SAME tint rather than three colors — and why the tint is
     rgba rather than a hex, since the whole effect is the overlap. */
  --gingham: rgba(100, 125, 94, 0.14);
  --gingham-cell: 21px;

  /* ---- the wildflowers ---- (see MEADOW in pages.ts)
     Decoration only. Nothing you can tap, and no figure, ever wears one. */
  --stem: #5c7a55;
  --bloom-cream: #fdf6e7;
  --bloom-gold: #f0b752;
  --bloom-rose: #dd7a92;
  --bloom-lilac: #9b86c9;

  /* What lifts a white card off the cloth. Without it the gingham runs edge to
     edge behind everything and the page reads as one busy sheet. */
  --lift: 0 1px 2px rgba(0, 0, 0, 0.04), 0 6px 18px rgba(0, 0, 0, 0.06);

  --control-h: 50px;
  --radius-lg: 16px;
  --radius-md: 12px;
  --gap: 10px;
  /* A row of controls stops here even on a wide screen. Without it a lone
     button on the media kit would stretch the full 620px column while a pair
     on the About page sat at half that, which is the "different sizes" Karli
     was looking at. */
  --row-max: 420px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  /* Longhands, not the `background:` shorthand. Safari drops a shorthand whose
     custom property expands to a multi-layer value, and this one is two layers,
     so the cloth would simply not appear on the phone she reviews on. */
  background-color: var(--page-bg);
  background-image:
    repeating-linear-gradient(
      90deg,
      var(--gingham) 0 var(--gingham-cell),
      transparent var(--gingham-cell) calc(var(--gingham-cell) * 2)
    ),
    repeating-linear-gradient(
      0deg,
      var(--gingham) 0 var(--gingham-cell),
      transparent var(--gingham-cell) calc(var(--gingham-cell) * 2)
    );
  color: var(--text);
  font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
}

.wrap {
  max-width: 620px;
  margin: 0 auto;
  padding: 0 16px calc(48px + env(safe-area-inset-bottom));
}

/* The one thing terracotta still does out here, and it matches her links app's
   own focus ring stroke for stroke — so tabbing across the four pages never
   changes what "this is the thing you are on" looks like. Green would be
   invisible against a green button, which is the whole reason the alarm color
   survives at all. */
a:focus-visible,
button:focus-visible,
summary:focus-visible {
  outline: 2.5px solid var(--mark);
  outline-offset: 2px;
}

/* ---- the white card, and it is the only one ----
 *
 * Karli, 2026-08-10, looking at the new cloth: "I like the gingham you used but
 * it seems like some of the words are hard to read. Maybe all the text should
 * have white boxes behind it." She was right, and the rule was already written
 * down at the top of this file — EVERY WORD SITS ON WHITE — it just was not
 * true of the three blocks that predate the cloth and had never needed to be
 * cards: the hero (her name, her tagline, her pitch and the two buttons), the
 * media kit's "By the numbers" heading, and the footer. Black text over a check
 * pattern reads fine on a laptop and badly on a phone in daylight, which is
 * where she looks at it.
 *
 * So the card is drawn HERE, once, and every block of text on the site is in
 * this selector list. A new block carrying prose either joins it or nests
 * inside `.section`; `pages.test.ts` reads this file and fails if any of the
 * four stops being painted, because a block quietly sitting on the cloth again
 * is exactly what she reported and nothing else here would notice.
 *
 * What is left of the gingham is the gaps between the cards and the strip down
 * each side, which is the look rather than a loss: cards laid out on a cloth.
 * The wildflowers still grow straight on it, because a flower is decoration and
 * decoration is the one thing the pattern cannot make harder to read. */

.hero,
.section,
.stats,
.foot {
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background: var(--card);
  box-shadow: var(--lift);
}

/* ---- shared type ---- */

.h2 {
  margin: 0 0 14px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}

.lede { margin: 0 0 18px; font-size: 17px; }
.body p { margin: 0 0 16px; }
.body p:last-child { margin-bottom: 0; }

/* ---- hero ---- */

/* Painted by the card rule above. This only places it — a margin rather than
   flush to the ribbon, so a band of cloth shows between the tabs and her name
   and the card reads as sitting ON something. */
.hero {
  margin: 20px 0 0;
  padding: 30px 20px 28px;
  text-align: center;
}

.hero__avatar {
  /* Block, not the default inline. The heading below is inline-block so its
     squiggle can hug the text rather than span the column — which means an
     inline image would share its line box and sit BESIDE the name. */
  display: block;
  margin: 0 auto;
  width: 104px;
  height: 104px;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--card);
  box-shadow: 0 0 0 2px var(--control);
}

.hero__name {
  position: relative;
  display: inline-block;
  margin: 18px 0 0;
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.015em;
}

/* Her hand-drawn underline — the same SVG her links page draws under her name
   there, so every heading on the site including that one matches. Sage green
   since 2026-08-10: she asked twice for one theme and this stroke was the last
   orange thing on the page. The markup is SQUIGGLE in pages.ts; this only
   places and colors it. */
.hero__squiggle {
  position: absolute;
  left: 0;
  bottom: -9px;
  width: 100%;
  height: 8px;
  color: var(--control);
  opacity: 0.9;
}

.hero__tagline {
  margin: 22px auto 0;
  max-width: 30em;
  font-size: 17px;
}

/* Karli's own opening paragraph — her /work page underneath, her box at /edit
   on top. A notch smaller than the tagline above it so the two read as a
   headline and the words under it, rather than as two competing statements —
   and wider, because it is prose.

   It is a wrapper around whatever paragraphs she wrote rather than one <p>,
   because it is filled by renderCopy like every other piece of her writing now.
   `:empty` matters: with nothing to say in either layer this would otherwise be
   a blank div holding its own top margin open under her tagline. */
.hero__pitch {
  margin: 14px auto 0;
  max-width: 34em;
  font-size: 15px;
  line-height: 1.65;
  text-align: left;
  color: var(--muted);
}

.hero__pitch:empty { display: none; }
.hero__pitch p { margin: 0 0 12px; }
.hero__pitch p:last-child { margin-bottom: 0; }

/* Only Karli ever sees these: a link beside a heading that opens the box in
   /edit which writes that block. She went looking for her opening paragraph on
   2026-08-10 and could not find it — "I can't find where that is to edit it" —
   and the surest answer to that is a way in from the words themselves rather
   than a better-labeled editor. Stripped from the page for everybody else by
   the <!--admin-only--> markers around them. */
/* The hero's edit link sits on a line of its own under her paragraph, because
   there is no heading up there to hang it off. */
.hero__editline { margin: 10px 0 0; }

.editlink {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--control);
  text-decoration: none;
  border-bottom: 1px solid var(--control);
  margin-left: 8px;
  white-space: nowrap;
}

/* ---- the wildflowers under the hero ----
 *
 * Markup is MEADOW in pages.ts, dropped in with a `<!--flowers-->` marker; this
 * only sizes and colors it. A centered row of sprigs with the overflow clipped,
 * so a phone shows about half of the twenty-two and every flower keeps its
 * shape — a single stretched band would squash them all as the column narrows.
 * `overflow: hidden` and not `auto`: auto on one axis implies auto on the
 * other, and one pixel of ink would paint a scrollbar across the page.
 *
 * Each sprig carries its OWN width and height as attributes, so this rule must
 * not set either: uneven widths are what stop the row reading as a picket
 * fence, and Karli asked for the first row — every flower the same height at
 * the same pitch — to be redone or removed. See the header over MEADOW. */

.meadow {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: 8px;
  height: 44px;
  /* Out to the column's padding edges, not inside them. The row is always
     wider than the space it has, so it is always clipped — and clipped at the
     edge of the screen it reads as a border running off the page, where
     clipped 16px short of it reads as a mistake. Negative margins rather than
     100vw: vw counts the desktop scrollbar and would overflow the page. */
  margin: 20px -16px 0;
  overflow: hidden;
}

.meadow__sprig { flex: none; }

.meadow__stem,
.meadow__blade {
  fill: none;
  stroke: var(--stem);
  stroke-linecap: round;
}

.meadow__stem { stroke-width: 1.5; }
/* Thinner than a stem, so the grass sits behind the flowers rather than
   competing with them. */
.meadow__blade { stroke-width: 1.1; }

.meadow__leaf { fill: var(--stem); }

.meadow__bloom--cream {
  fill: var(--bloom-cream);
  /* The one bloom that is nearly the color of the cloth, so it gets an outline
     or it disappears over a white square of the gingham. */
  stroke: var(--stem);
  stroke-width: 0.6;
  stroke-opacity: 0.45;
}

.meadow__bloom--gold { fill: var(--bloom-gold); }
.meadow__bloom--rose { fill: var(--bloom-rose); }
.meadow__bloom--lilac { fill: var(--bloom-lilac); }

.meadow__eye { fill: var(--bloom-gold); }
.meadow__eye--pale { fill: var(--bloom-cream); }
.meadow__eye--dark { fill: var(--stem); }

/* ---- controls: buttons and social links, one size ----
 *
 * `.actions` and `.socials` are the SAME grid, so whatever sits in one of them
 * comes out the same height and — within a row — exactly the same width, no
 * matter how long its label is. auto-fit rather than flex is the whole trick:
 * with flex, two buttons reading "Shop my links" and "Work with me" size
 * themselves to their text and land visibly unequal, which is what Karli
 * photographed on the About page.
 *
 * One column below about 330px of usable width, so a phone stacks rather than
 * squeezing two labels into halves that wrap. */

.actions,
.socials {
  margin: 26px auto 0;
  max-width: var(--row-max);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--gap);
}

.btn,
.social {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--control-h);
  /* 12px, not 16: two of these sit in a 154px column inside a card at 390px,
     and at 16px "@karli_yarber" ellipsized and "DM on Instagram" wrapped to two
     lines beside a one-line button. Measured, not guessed — see the shots. */
  padding: 8px 12px;
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  min-width: 0;
}

.btn {
  border-color: var(--control);
  background: var(--control);
  color: var(--control-text);
}

/* The second-choice button, and the one every social link matches. */
.btn--ghost,
.social {
  background: transparent;
  border-color: var(--control);
  color: var(--text);
}

.btn:active,
.social:active { transform: translateY(1px); }

/* ---- socials (sized above, beside the buttons they sit next to) ---- */

.social svg {
  flex: none;
  width: 20px;
  height: 20px;
  fill: var(--control);
}

.social__handle {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- the live "just posted about" strip ---- */

.feature {
  display: block;
  margin: 30px 0 0;
  padding: 16px 18px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  border-left: 4px solid var(--control);
  background: var(--card);
  box-shadow: var(--lift);
  color: var(--text);
  text-align: left;
  text-decoration: none;
}

.feature__kicker {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--control);
}

.feature__brand {
  display: block;
  margin-top: 4px;
  font-size: 19px;
  font-weight: 700;
}

.feature__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-top: 2px;
  font-size: 14px;
}

.feature__discount { font-weight: 700; color: var(--control); }
.feature__blurb { color: var(--muted); }

.feature__code {
  display: inline-block;
  margin-top: 10px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--page-bg);
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

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

/* Every block of the site is this card, with no exceptions and no variants —
   including the About page's prose, which used to be the one bare block on the
   site and read as a different page because of it. */
.section {
  margin: 40px 0 0;
  padding: 22px 20px;
}

/* ---- media kit ---- */

.stats {
  margin: 40px 0 0;
  padding: 22px 20px;
}

.stats__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--gap);
}

/* Tiles INSIDE the numbers card now that the card exists, so they take the pale
   fill `.chips li` uses rather than white-on-white with a shadow that would
   have them floating off a surface they are already on. */
.stat {
  padding: 16px 14px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background: var(--page-bg);
  text-align: center;
}

.stat__value { display: block; font-size: 24px; font-weight: 700; }
.stat__label { display: block; margin-top: 2px; font-size: 13px; color: var(--muted); }
.stat__note { display: block; margin-top: 4px; font-size: 11px; color: var(--muted); opacity: 0.8; }

/* Karli's own "Numbers as of ..." line, read off her /work page with them. */
.stats__asof { margin: 10px 0 0; font-size: 12px; color: var(--muted); }

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.chips li {
  padding: 7px 13px;
  border-radius: 999px;
  background: var(--page-bg);
  border: 1px solid var(--line);
  font-size: 14px;
  font-weight: 600;
}

/* ---- her work: sample posts, and the brands she has worked with ----
 *
 * Both blocks are read live off her own media kit at /work on the links app,
 * because that is where she wrote them and where she will keep editing them.
 * The thumbnails are served by her app too — a root-relative /media/... path
 * that this server proxies straight through, so no image is ever copied here. */

.samples__format {
  margin: 18px 0 10px;
  font-size: 15px;
  font-weight: 700;
}

.samples__format:first-child { margin-top: 0; }

.samples {
  display: grid;
  /* Two across a 390px phone, more as the column widens. */
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: var(--gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.sample__body {
  display: block;
  color: var(--text);
  text-decoration: none;
}

.sample__frame {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--page-bg);
  border: 1px solid var(--line);
}

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

/* A post whose thumbnail her app did not give us still gets a tile, so the
   grid does not develop holes. */
.sample__frame--bare {
  display: flex;
  align-items: center;
  justify-content: center;
}

.sample__metric {
  position: absolute;
  left: 6px;
  bottom: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

.sample__frame--bare .sample__metric {
  position: static;
  background: var(--card);
  color: var(--muted);
}

.sample__where {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  color: var(--muted);
}

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

.collabs > li { margin: 0 0 var(--gap); }

.collab {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--page-bg);
}

.collab__summary {
  display: block;
  /* Matches every other tappable thing on the site. */
  min-height: var(--control-h);
  padding: 11px 34px 11px 14px;
  position: relative;
  cursor: pointer;
  list-style: none;
}

.collab__summary::-webkit-details-marker { display: none; }

/* The browser's own disclosure triangle is replaced with a chevron that turns,
   so it is obvious there is something behind the row before you tap it. */
.collab__summary::after {
  content: "";
  position: absolute;
  right: 14px;
  top: 50%;
  width: 8px;
  height: 8px;
  margin-top: -6px;
  border-right: 2px solid var(--control);
  border-bottom: 2px solid var(--control);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}

.collab[open] .collab__summary::after {
  margin-top: -2px;
  transform: rotate(225deg);
}

.collab--flat {
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--page-bg);
}

.collab__brand { display: block; font-weight: 700; }

.collab__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 12px;
  margin-top: 1px;
  font-size: 13px;
  color: var(--muted);
}

.collab__count { font-weight: 600; color: var(--control); }

.collab__detail {
  padding: 0 14px 14px;
  border-top: 1px solid var(--line);
  margin-top: -1px;
}

.collab__work { margin: 14px 0 0; font-weight: 600; }
.collab__outcome { margin: 4px 0 0; color: var(--muted); font-size: 14px; }
.collab__detail .samples { margin-top: 14px; }

/* Only when both are on the page: the dropdowns above, then the standing list
   of names. With no live collabs the chips sit on their own. */
.collabs + .chips { margin-top: 16px; }

/* The "Most asked" questions on the front page ARE the dropdowns above — same
   border, same chevron, same tap target — because two disclosures that look
   slightly different is exactly the inconsistency Karli reported in August.
   The answer's own spacing is the only thing this block adds, and it matches
   .collab__work's, which is the other first child of a .collab__detail. */
.qa__answer { margin: 14px 0 0; }

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

.offers li {
  position: relative;
  padding: 0 0 0 22px;
  margin: 0 0 12px;
}

.offers li:last-child { margin-bottom: 0; }

.offers li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 9px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--control);
}

.offers strong { display: block; }
.offers span { color: var(--muted); font-size: 14px; }

/* ---- footer ----
 *
 * One footer, rendered by renderFooter in pages.ts and identical on all three
 * pages. It used to be written into each page file, which is how they ended up
 * with three different sign-offs above the same address. */

.foot {
  margin: 44px 0 0;
  padding: 22px 20px;
  text-align: center;
  font-size: 14px;
  color: var(--muted);
}

/* Both selectors carry .foot so neither can lose to the other on specificity —
   a bare `.foot__mail { margin-top }` would be beaten by `.foot p`. */
.foot p { margin: 0; }
.foot .foot__note { font-weight: 600; }
.foot .foot__mail { margin-top: 4px; }

.foot__mail a {
  color: var(--text);
  text-decoration: none;
  border-bottom: 2px solid var(--control);
  padding-bottom: 1px;
}

/* Only rendered for someone who can actually use it — the server strips this
   for everyone else, so the public page has no trace of an admin panel. */
.owner {
  margin: 26px 0 0;
  font-size: 13px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  justify-content: center;
}

.owner a {
  color: var(--text);
  opacity: 0.5;
  text-decoration: none;
  border-bottom: 1px solid var(--control);
  padding-bottom: 1px;
}

/* ---- the tab ribbon ----
 *
 * This used to be site/ribbon.css, a separate file that could not use a single
 * one of the custom properties above: it was injected into Karli's links app,
 * a document with its own stylesheet and its own resets, so every value had to
 * be written out literally. That injection is gone (she asked for it — /links
 * is her bio link and should send people to her links, not back around the
 * site), and with it the reason for two stylesheets. One file, one palette.
 *
 * THE TABS ARE THE SAME OBJECT AS THE BUTTONS, and that is Karli's second note
 * of 2026-08-10: "the menu ribbon buttons are still standing apart from the
 * theme you created." They were text at 50% opacity with a small stroke under
 * whichever one you were on — the only thing on a site of outlined and filled
 * green controls that did not look like a control at all. So a tab is now a
 * pill with the same 2px green border and the same --radius-md as `.btn`, the
 * current one is filled exactly like `.btn` and the rest are outlined exactly
 * like `.btn--ghost`. Which page you are on is now the same "this one is
 * filled" signal the buttons already use.
 *
 * The hand-drawn stroke that used to mark the current tab is gone with it: a
 * filled green pill already says which page you are on, and a terracotta
 * squiggle across it would have been a second answer to a settled question.
 * Her mark still opens every page, under the heading. */

.ribbon {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(244, 244, 244, 0.94);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--line);
}

.ribbon__inner {
  max-width: 620px;
  margin: 0 auto;
  padding: 9px 8px;
  display: flex;
  gap: 6px;
  /* Four tabs fit at 390px with room to spare, but a longer label added later
     should scroll rather than break the page open. `safe` keeps the first tab
     reachable when it does overflow — plain `center` clips the left end. */
  justify-content: safe center;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.ribbon__inner::-webkit-scrollbar {
  display: none;
}

.ribbon__tab {
  flex: none;
  padding: 9px 13px;
  border: 2px solid var(--control);
  border-radius: var(--radius-md);
  background: var(--card);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}

.ribbon__tab.is-current {
  background: var(--control);
  color: var(--control-text);
}

.ribbon__tab:active { transform: translateY(1px); }

/* ---- on paper, and in a PDF ----
 *
 * `/media-kit` is the page Karli sends to brands, and "send" often means the
 * brand hits Save as PDF — or she does, to attach one to an email. That copy
 * was missing the most persuasive thing on the page. Measured against the live
 * site on 2026-09-21, printing it produced six sheets on which:
 *
 *   * every brand dropdown was a collapsed chevron, so "$3500 in sales",
 *     "584k views on Tiktok with 40k likes" and "12 sales with 100% conversion
 *     from clicks" — her proof of past work, and the reason a brand says yes —
 *     were in NO printed copy of her media kit;
 *   * the tab ribbon printed at the top of sheet one, four dead pills;
 *   * the gingham cloth printed behind all six sheets;
 *   * her filled green buttons printed as white text on a fill the browser
 *     drops unless "Background graphics" is ticked, so "Start a conversation"
 *     and "Email me" came out invisible;
 *   * and when SHE saves it — she is the likeliest person to — every owner-only
 *     "Edit" link and "Sign out" was on it.
 *
 * None of that throws, 404s or fails any other check; it is only ever seen by
 * whoever opens the file. So the rules below are a deliberate second layout,
 * and `pages.test.ts` pins the three that would go quietly wrong again.
 *
 * Two things here are decisions rather than details:
 *
 *   * A closed <details> is opened TWO ways on purpose. Chrome 131+ and Safari
 *     18.4+ hide the content with `content-visibility` on `::details-content`,
 *     which the older `display` override cannot reach; browsers before that use
 *     the slot, which `::details-content` does not exist on. Both rules are
 *     harmless where they do not apply, and a brand's browser is not ours to
 *     choose. Verified in Chrome: the first dropdown goes 68.5px → 348.8px.
 *   * Nothing here expands a link into its own URL. This file is read as a PDF
 *     far more often than on paper, and a PDF keeps its hyperlinks live — so
 *     printing "https://www.instagram.com/reel/DbwObGYBCQ5/?igsh=MTg2aDk0…"
 *     after every sample would make her media kit uglier for the common case
 *     and help only the rare one. Her address is spelled out in the footer.
 *
 * The thumbnails are `loading="lazy"` and do NOT appear in a screenshot taken
 * with the print media emulated — but they DO print: Chrome force-loads lazy
 * images on the print path, measured as 32 embedded JPEGs in the real PDF.
 * Do not "fix" that by dropping the lazy attribute; it would cost her phone
 * visitors the bytes to fix nothing. */
@media print {
  @page { margin: 14mm; }

  /* Paper is already white, and the cloth is 250-odd square inches of ink. */
  body {
    background-color: #fff;
    background-image: none;
  }

  /* Anything to tap is worth nothing on a sheet of paper: the tabs go, and so
     does every owner-only door, which is otherwise on the copy SHE saves. */
  .ribbon,
  .editlink,
  .hero__editline,
  .owner { display: none; }

  /* No card is floating off a cloth that is no longer there. */
  .hero,
  .section,
  .stats,
  .foot,
  .feature {
    box-shadow: none;
    border: 1px solid var(--line);
  }

  /* Her whole media kit, not the half that happens to be expanded. See above
     for why this is two rules. */
  details::details-content { content-visibility: visible; }
  details:not([open]) > *:not(summary) { display: block; }
  /* The chevron says "there is more behind this", which is a lie on paper. */
  .collab__summary::after { display: none; }
  .collab__summary { padding-right: 14px; }

  /* A filled button prints as white-on-white the moment the reader leaves
     "Background graphics" unticked, which is the default. Outlined survives
     either way, and the label is the part that matters. */
  .btn {
    background: none;
    color: var(--text);
  }

  /* Keep a unit of meaning on one sheet where it fits. Deliberately NOT
     `.section`, which would push a long one onto a fresh page and leave half
     a sheet blank. */
  .hero,
  .stat,
  .collab,
  .sample,
  .offers li { break-inside: avoid; }
  .h2 { break-after: avoid; }
}
