/* GENERATED by modules/text/scripts/integrate.mjs from public/styles.css — do not edit.
   Everything is nested in .mod-text (the <main> of the text pages) so GlyPop's page rules
   (body, :root colours, .brand, .topbar, ...) stay inside the text area. 3 @keyframes hoisted. */
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dirBounce {
  0%, 62%, 100% { transform: translateY(0); }
  70% { transform: translateY(-5px); }
  77% { transform: translateY(0); }
  84% { transform: translateY(-2px); }
  91% { transform: translateY(0); }
}
@keyframes dirStickerRock {
  0%, 66%, 100% { transform: rotate(0deg) translateY(0); }
  74% { transform: rotate(-4deg) translateY(-2px); }
  83% { transform: rotate(3deg) translateY(0); }
  92% { transform: rotate(-1deg); }
}
.mod-text {
/* GlyPop - cream paper, dark ink, and a stationery palette of strawberry pink,
   lilac and butter. The colour lives in the surfaces behind the samples: every
   result and every tool sample is still plain dark ink on a pale fill, which is
   what keeps a wall of decorated characters readable. */
& {
  --bg: #fff9f4;
  --soft: #fdf2f7;
  --ink: #292431;
  --muted: #625967;
  --line: #eadde5;
  --pink: #c93668;
  --blue: #0f57db;
  --radius: 14px;
  --tap: 44px;

  /* The three pale washes of the brief, plus two blends of the same three, so
     all five sections can be told apart without inventing a new hue. Each one
     is paired with a dark ink of its own for borders and small details - the
     text on a wash is always --ink. */
  --wash-berry: #ffe5ed;
  --wash-lilac: #f2eafe;
  --wash-butter: #fff2d8;
  --wash-petal: #f8e7f3;
  --wash-peach: #ffeadd;
  /* A sixth wash, for the sixth family of home cards: the picture tools are the
     one group the five above have no colour left for. */
  --wash-mint: #e4f5ea;
  --edge-berry: #a82c56;

  --shadow: 0 4px 14px rgba(88, 48, 75, 0.07);
  --shadow-lift: 0 8px 20px rgba(88, 48, 75, 0.13);

  /* Five small ornaments, drawn here as data URIs: no image file is fetched,
     no font is downloaded, and nothing outside this stylesheet is needed to
     paint one. They are decoration - every one of them sits on a pseudo
     element or an aria-hidden node, beside a label that still carries the
     meaning. */
  --orn-sparkle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 1.8c1.1 5.6 3 7.5 8.6 8.6-5.6 1.1-7.5 3-8.6 8.6-1.1-5.6-3-7.5-8.6-8.6 5.6-1.1 7.5-3 8.6-8.6Z' fill='%23dcc8f7' stroke='%237a50b5' stroke-width='1.2' stroke-linejoin='round'/%3E%3Ccircle cx='19.4' cy='19.2' r='2.1' fill='%23f6a7c4'/%3E%3C/svg%3E");
  --orn-star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.4 14.9 8.7 21.7 9.5 16.7 14.2 18 20.9 12 17.6 6 20.9 7.3 14.2 2.3 9.5 9.1 8.7Z' fill='%23ffd9ae' stroke='%23c05a2b' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }

& {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  overflow-wrap: break-word;
}

& {
  font-family: "Pretendard", "Apple SD Gothic Neo", "Malgun Gothic", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 { line-height: 1.3; }
h2 { font-size: 17px; margin: 0 0 2px; }
h3 { font-size: 15px; margin: 0 0 4px; }

a { color: var(--blue); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--blue);
  color: #fff;
  padding: 10px 14px;
  z-index: 20;
  border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

:where(button, textarea, a, [tabindex]):focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
  border-radius: 8px;
}

/* ---------- top bar ---------- */
/* The same bar on every page - the app, the workshop and the four static
   pages - so the ribbon under it is the one thing that says "still GlyPop"
   wherever a tool takes you. */
.topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: rgba(255, 249, 244, 0.94);
  backdrop-filter: blur(6px);
  z-index: 10;
}
/* A 2px ribbon of the three washes. Decoration with no text in it. */
.topbar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: linear-gradient(90deg,
    var(--wash-berry) 0%, var(--wash-petal) 28%, var(--wash-lilac) 55%,
    var(--wash-peach) 80%, var(--wash-butter) 100%);
}

.brand { display: flex; align-items: center; gap: 9px; min-width: 0; flex: 1; }
/* On the static pages the same brand block is the way back to the tool. */
a.brand { color: inherit; text-decoration: none; }
a.brand:hover .brand__name { color: var(--pink); }
.brand__logo { flex: none; display: block; }
.brand__text { display: flex; flex-direction: column; min-width: 0; }
.brand__name { font-weight: 700; font-size: 16px; letter-spacing: -0.01em; line-height: 1.25; }
/* The tagline wraps rather than trailing off: at 390px the three-part
   descriptor must stay whole next to the language button. */
.brand__tagline {
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted);
  word-break: keep-all;
  overflow-wrap: break-word;
}

.lang-btn {
  flex: none;
  min-height: var(--tap);
  padding: 6px 12px;
  font-size: 14px;
  font-weight: 600;
  color: var(--pink);
  background: #fff;
  border: 1px solid var(--pink);
  border-radius: 999px;
  cursor: pointer;
}
.lang-btn:hover { background: var(--wash-berry); }
/* On the static pages the language switch is a link, not a button. */
a.lang-btn { display: inline-flex; align-items: center; text-decoration: none; }

/* ---------- layout ---------- */
.wrap {
  max-width: 900px;
  margin: 0 auto;
  padding: 10px 14px 32px;
}

/* ---------- tool head ---------- */
/* A tool screen opens with the way back, its name and one line about what it
   does - above the input box, so the screen says what it is before it asks
   for anything. On the gallery the whole block is hidden. */
.tool-head { margin: 0 0 10px; }
.tool-head[hidden] { display: none; }
.tool-head__back { margin: 0 0 4px; font-size: 14px; font-weight: 600; }
.tool-head h1 { margin: 0; font-size: 19px; line-height: 1.3; }
.tool-head .note { margin: 3px 0 0; }

/* ---------- input ---------- */
.input-panel {
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 9px 10px 8px;
  margin-bottom: 8px;
}
.input-panel[hidden] { display: none; }

.input-panel__head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 5px;
}
/* The label gives way first so the undo button never pushes this row wider
   than a 390px screen. */
.input-panel__head label {
  font-size: 14px;
  font-weight: 700;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#inputText {
  width: 100%;
  min-height: 60px;
  padding: 8px 10px;
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  resize: vertical;
}
#inputText:focus { border-color: var(--blue); }

/* One short line. The full privacy wording lives in the help disclosure. */
.input-panel__meta {
  margin: 5px 0 0;
  font-size: 12px;
  color: var(--muted);
}
#inputCount {
  flex: none;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--muted);
}
#inputCount.is-limit { color: var(--pink); }

/* ---------- undo ---------- */
/* Present only while there is something to take back, so it costs no room
   the rest of the time. */
.undo-btn {
  flex: none;
  color: var(--blue);
  border-color: #bcd2ff;
  background: #f1f6ff;
  white-space: nowrap;
}
.undo-btn:hover { background: #e4edff; }
.undo-btn[hidden] { display: none; }

/* ---------- sample chips ---------- */
/* Collapsed by default: one 13px line under the box, no block of its own. */
.samples { margin: 4px 0 0; }
.samples > summary {
  display: inline-block;
  padding: 4px 2px;
  font-size: 13px;
  font-weight: 600;
  color: var(--blue);
  cursor: pointer;
}
.samples > summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; border-radius: 8px; }

.samples__row { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 0; }
.samples__note { margin: 6px 0 2px; font-size: 12px; line-height: 1.45; color: var(--muted); }

.chip-btn {
  min-height: var(--tap);
  max-width: 100%;
  padding: 6px 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  background: #fff;
  border: 1px dashed var(--line);
  border-radius: 999px;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chip-btn:hover { border-style: solid; border-color: var(--pink); color: var(--ink); background: var(--wash-berry); }
/* A chip that records a choice (the kit presets) shows it. The label stays
   dark ink on the pale fill; the pink is the border and the weight. */
.chip-btn[aria-pressed="true"] {
  border-style: solid;
  border-color: var(--pink);
  color: var(--ink);
  font-weight: 700;
  background: var(--wash-berry);
}

.muted { color: var(--muted); }

/* ---------- status / manual copy ---------- */
.status {
  min-height: 20px;
  margin: 0 0 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--blue);
}
.status.is-error { color: var(--pink); }

.manual-copy {
  border: 1px solid var(--pink);
  border-radius: var(--radius);
  padding: 10px;
  margin-bottom: 12px;
  background: var(--wash-berry);
}
.manual-copy label { display: block; font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.manual-copy textarea {
  width: 100%;
  font: inherit;
  font-size: 16px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  resize: vertical;
}

/* ---------- panels ---------- */
.panel { animation: fade 0.12s ease-out; }
.panel[hidden] { display: none; }


.note { margin: 0 0 8px; font-size: 14px; line-height: 1.4; color: var(--muted); }
.note--soft { font-size: 13px; }

/* Heading + one-line note, kept tight so the first result stays high up. */
.panel__head { margin-bottom: 8px; }
.panel__head .note { margin: 0; }

.example-hint {
  border-left: 3px solid var(--pink);
  background: var(--wash-berry);
  border-radius: 0 8px 8px 0;
  padding: 6px 9px;
  margin: 0 0 6px;
  color: var(--ink);
  font-size: 13px;
}
.example-hint[hidden] { display: none; }

/* Only shown once something is starred; empty favorites collapse to one line.
   One thin, definite line rather than the heavy dashes it used to wear: the
   shelf is a place on the page, not a box drawn around the tools. */
.fav-block {
  border: 1px solid #ecdcf3;
  border-radius: 16px;
  padding: 7px 10px 9px;
  margin: 0 0 10px;
  background: linear-gradient(180deg, var(--wash-lilac) 0%, #fffdfe 62%);
}
.fav-block[hidden] { display: none; }
.fav-block__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
/* A small ornament beside the two saved-tool headings, never in place of the
   words: the heading still reads as text to anything that is not looking. */
.fav-block__head h2 {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  /* One level under the screen's title, but a saved-tools row is a shelf
     rather than a chapter, so it keeps the smaller size it always had. */
  font-size: 15px;
}
.fav-block__head h2::before {
  content: "";
  flex: none;
  width: 16px; height: 16px;
  background: var(--orn-star) center / contain no-repeat;
}
.recent .fav-block__head h2::before { background-image: var(--orn-sparkle); }
.fav-block__actions { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; }
.fav-block .cards { margin-top: 8px; }

.fav-hint {
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--muted);
}
.fav-hint[hidden] { display: none; }

/* ---------- shape filter (fancy) ---------- */
/* One wrapping row: three chips plus the count. It replaces no existing row,
   so it is kept to a single line height on a 390px screen. */
.group-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin: 0 0 6px;
}
.group-bar__chips { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.chip-btn--group { border-style: solid; }
/* The chips past the first few, folded away behind one button. Forty-six mood
   chips at 390px were four wrapped rows above the first face; six and a button
   are one. The region takes a whole row of its own once it opens, so the
   chips that appear are clearly the ones the button just revealed. */
.group-bar__more {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  flex-basis: 100%;
  min-width: 0;
}
.group-bar__more[hidden] { display: none; }
.chip-btn--more { border-style: dashed; font-weight: 600; }
.chip-btn--group[aria-pressed="true"] {
  border-color: var(--pink);
  color: var(--ink);
  font-weight: 700;
  background: var(--wash-berry);
}

/* Only rendered once a group is picked, so the default view stays short. */
.group-note {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}
.group-note[hidden] { display: none; }

/* ---------- keep handles and links as typed ---------- */
/* One checkbox above the grid. It changes what every card below produces, so
   it sits with the filters rather than inside a card. */
.linksafe-bar { margin: 0 0 10px; }
.linksafe-bar__row {
  display: flex;
  align-items: center;
  gap: 8px;
  /* A 44px target on a phone without making the row look like a button. */
  min-height: 44px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.linksafe-bar__row input { width: 18px; height: 18px; flex: none; accent-color: var(--pink); }
.linksafe-bar .note { margin: 0; font-size: 12px; line-height: 1.45; }

/* What the catalogue is, and what the switch above really does. Both are
   explanations rather than results, so at 390px they cost one line here and
   the first sample stays on the first screen. A caption, not a panel. */
.fancy-help { margin: 0; }
.fancy-help > summary {
  display: inline-block;
  padding: 4px 2px;
  font-size: 13px;
  font-weight: 600;
  color: var(--blue);
  cursor: pointer;
}
.fancy-help > summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; border-radius: 8px; }
.fancy-help > .note { margin: 4px 0 0; }

/* ---------- paste-check, folded away ---------- */
/* Last thing in the panel and closed by default: it is a procedure for the
   user to run on their own phone, not a result this site can produce. */
.paste-check {
  margin: 16px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #fff;
}
.paste-check > summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.paste-check .note { margin: 6px 0; }
.paste-check__steps {
  margin: 6px 0;
  padding-left: 20px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}
.paste-check__steps li { margin: 0 0 6px; }

/* Same folded shell for "what do these numbers count?". One explanation for
   the whole grid, so a card only has to carry the number itself. */
.length-check__units {
  margin: 6px 0;
  padding-left: 20px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}
.length-check__units li { margin: 0 0 6px; }

/* ---------- show more ---------- */
/* The decoration catalogue is long, so the grid lays out one screenful and
   this row asks for the next. The count beside the button always says how
   much of the chip is on the page, so nothing looks lost. */
.more-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin: 2px 0 14px;
}
.more-bar[hidden] { display: none; }
#fancyEmpty[hidden] { display: none; }
#decorateEmpty[hidden] { display: none; }

/* ---------- result cards ---------- */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
  gap: 8px;
  margin-top: 0;
}

.card {
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 9px 12px 10px;
  background: #fff;
  box-shadow: var(--shadow);
  min-width: 0;
}
.card:hover { border-color: #e0b9cb; }
.card:focus-within { border-color: var(--blue); }
.card.is-example { background: #fffaef; border-style: dashed; border-color: #f0dcbb; }

/* Quiet label row: the name is a caption for the result, not the headline. */
.card__head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 3px;
  min-width: 0;
}
.card__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.badge {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--muted);
  background: #fff;
}
.badge--example { color: var(--edge-berry); border-color: var(--pink); background: var(--wash-berry); }
.badge--good { color: var(--blue); border-color: #bcd2ff; background: #f1f6ff; }
.badge--mixed { color: #8a6a00; border-color: #efdca6; background: #fffaeb; }
.badge--limited { color: var(--edge-berry); border-color: #f0a9c3; background: var(--wash-berry); }
/* "원문 그대로" vs "글자 바뀜" on the 한글 꾸미기 cards. Quiet grey for the
   wrappers, blue for the transforms - the pair the reader has to tell apart. */
.badge--keep { color: var(--muted); border-color: var(--line); background: #fafbfd; }
.badge--change { color: var(--blue); border-color: #bcd2ff; background: #f1f6ff; }
/* "문자 스타일" vs "조합 스타일" on the 영문 꾸미기 cards: whether Unicode
   really has this letterform, or GlyPop composed the look here. Quiet on
   purpose - it is a fact about the style, not a recommendation. */
.badge--family { font-weight: 600; color: var(--muted); border-color: var(--line); background: #fafbfd; }
.badge--glyph { color: var(--ink); }

.star-btn {
  flex: none;
  width: var(--tap); height: var(--tap);
  font-size: 16px;
  line-height: 1;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 13px;
  cursor: pointer;
  color: var(--muted);
  /* Colour only, and only on the way in: nothing here moves, so the reduced
     motion query below has nothing of its own to switch off. */
  transition: background-color 0.14s ease-out, border-color 0.14s ease-out, color 0.14s ease-out;
}
.star-btn:hover { border-color: var(--pink); color: var(--edge-berry); background: #fff5f8; }
.star-btn:active { background: var(--wash-berry); }
/* Starred: a filled star on a bright fill rather than a pale wash, with a white
   ring inside the border so the shape stays crisp against a card of any colour.
   The glyph is already a different character - this is what makes the
   difference readable at a glance, across a row and on a phone. */
.star-btn[aria-pressed="true"] {
  color: var(--edge-berry);
  border-color: var(--pink);
  background: #ffd6e3;
  font-size: 18px;
  box-shadow: inset 0 0 0 1.5px #fff;
}
.star-btn[aria-pressed="true"]:hover { background: #ffc6d8; }
.star-btn[aria-pressed="true"]:active { background: #ffb9cf; }

/* The result is the biggest, darkest thing on the card. */
.card__out {
  display: block;
  font-size: 18px;
  line-height: 1.5;
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  margin-bottom: 8px;
  min-height: 27px;
}
.card.is-example .card__out { color: var(--muted); }

/* Truthful per-style caveat: only the styles that change more than the Latin
   letters carry one. */
.card__note {
  margin: -2px 0 8px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}

/* Copy leads, the length sits next to it, the star at the far end. */
.card__foot { display: flex; align-items: center; gap: 8px; }
.card__foot .star-btn { margin-left: auto; }

/* How long this card's result is. Quiet on purpose - it is a fact about the
   output, not an instruction, and it must never outweigh the result above. */
.card__len {
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.primary-btn {
  min-height: var(--tap);
  padding: 8px 18px;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  color: #fff;
  background: var(--pink);
  border: 1px solid var(--pink);
  border-radius: 999px;
  cursor: pointer;
}
.primary-btn:hover { background: #ad2b57; border-color: #ad2b57; }

.ghost-btn {
  min-height: var(--tap);
  padding: 7px 12px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
}
.ghost-btn:hover { border-color: var(--blue); color: var(--blue); }
.ghost-btn[aria-pressed="true"] { border-color: var(--blue); color: var(--blue); background: #f1f6ff; }
/* Same 44px touch target, just narrower and quieter. */
.ghost-btn--small { padding: 6px 10px; font-size: 13px; }

/* ---------- text kits ---------- */
/* A kit card holds several lines, so the result is set a little smaller than
   on the one-line tabs and wraps rather than pushing the card wide. The
   pre-wrap / anywhere pair on .card__out is what keeps a long Korean line
   inside the card instead of breaking the layout. */
.card--kit .card__out {
  font-size: 16px;
  line-height: 1.65;
}

.mixer {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fffdfe;
  padding: 10px 10px 4px;
  margin: 0 0 12px;
  min-width: 0;
}
.mixer[hidden] { display: none; }
.mixer > h2 { font-size: 16px; margin-bottom: 2px; }
.mixer .filter-bar { margin-top: 6px; }
.mixer__actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 8px; }
#kitMixOut { margin-bottom: 8px; }

/* ---------- search / filter ---------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}
.field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.field--grow { flex: 1 1 190px; }
.field label { font-size: 14px; font-weight: 600; }
.field input,
.field select {
  width: 100%;
  max-width: 100%;
  min-height: var(--tap);
  padding: 6px 9px;
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
}
.field select { min-width: 150px; }
.field input:focus, .field select:focus { border-color: var(--blue); }

/* ---------- symbols ---------- */
.symbol-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.chip-note { font-size: 13px; color: var(--muted); }
/* Said out loud in every favorites block while storage is unreachable. */
.chip-note--warn { color: var(--ink); font-weight: 600; }
.chip-note--count { font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }

.symbol-bar__title { font-size: 16px; margin: 0; }

.symbol-cat { margin: 0 0 12px; }
/* A category label inside a long list of symbols: small, like a shelf tag. */
.symbol-cat h3 { font-size: 15px; margin-bottom: 6px; }

.symbol-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(46px, 1fr));
  gap: 6px;
}

.symbol-btn {
  min-height: var(--tap);
  font-size: 20px;
  line-height: 1;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  color: var(--ink);
  padding: 4px;
}
.symbol-btn:hover { border-color: var(--pink); background: var(--wash-berry); }
/* .is-fav, not [aria-pressed], because a starred symbol looks starred in every
   mode while only favourites mode makes the button a toggle. */
.symbol-btn[aria-pressed="true"],
.symbol-btn.is-fav { border-color: var(--pink); background: var(--wash-berry); }

.empty-note { font-size: 13px; color: var(--muted); margin: 4px 0; }

/* ---------- symbol board ---------- */
/* Bigger type than the main input on purpose: what lands here is decoration,
   and a row of 16px symbols is hard to check before copying it. */
#boardText {
  width: 100%;
  min-height: 60px;
  padding: 8px 10px;
  font: inherit;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  resize: vertical;
  overflow-wrap: anywhere;
}
#boardText:focus { border-color: var(--blue); }
#boardCount { font-variant-numeric: tabular-nums; margin: 4px 0 8px; }
#boardUndo[hidden] { display: none; }

/* ---------- themed symbol sets ---------- */
/* A themed entry is usually several characters wide ("｡･:*:･ﾟ♡"), so the cell
   is a wide tile with the string at reading size and its name underneath -
   not the 46px square the single-character grid uses. */
.theme-sets { margin: 0 0 14px; min-width: 0; }
.theme-sets > h2 { font-size: 16px; margin-bottom: 2px; }
.theme-sets .fav-block__head h3 { font-size: 15px; }

/* The counts stay on the screen; how they were counted is one tap away, so on
   a narrow phone the sets themselves start a paragraph higher. No box around
   it - this is a caption, not a panel. */
.theme-basis { margin: 2px 0 8px; }
.theme-basis > summary {
  display: inline-block;
  padding: 4px 2px;
  font-size: 13px;
  font-weight: 600;
  color: var(--blue);
  cursor: pointer;
}
.theme-basis > summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; border-radius: 8px; }
.theme-basis > .note { margin: 2px 0 0; }

.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(112px, 100%), 1fr));
  gap: 6px;
}

.theme-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: var(--tap);
  padding: 6px 4px;
  font: inherit;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  min-width: 0;
}
.theme-btn__glyph {
  font-size: 19px;
  line-height: 1.35;
  max-width: 100%;
  overflow-wrap: anywhere;
}
.theme-btn__name {
  font-size: 11px;
  color: var(--muted);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.theme-btn:hover { border-color: var(--pink); background: var(--wash-berry); }
.theme-btn[aria-pressed="true"],
.theme-btn.is-fav { border-color: var(--pink); background: var(--wash-berry); }
.theme-btn[aria-pressed="true"] .theme-btn__name,
.theme-btn.is-fav .theme-btn__name { color: var(--ink); }

/* While a search is running the chips stop being a selection and show where
   the matches are, so the ones with nothing in them step back. */
.chip-btn--dim { opacity: 0.55; }

/* ---------- faces ---------- */
.face-preview {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
  background: var(--soft);
  text-align: center;
  margin-bottom: 14px;
}
.face-preview__label { display: block; font-size: 13px; color: var(--muted); }
.face-preview__out {
  display: block;
  font-size: 30px;
  line-height: 1.6;
  margin: 6px 0 10px;
  overflow-wrap: anywhere;
}
.face-preview__actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }

.part-group {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px;
  margin: 0 0 12px;
  min-width: 0;
}
.part-group legend { font-size: 14px; font-weight: 700; padding: 0 6px; }

.part-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(92px, 100%), 1fr));
  gap: 6px;
}

.part-btn {
  min-height: var(--tap);
  padding: 6px 4px;
  font: inherit;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
}
.part-btn__glyph { font-size: 18px; line-height: 1.2; }
.part-btn__name { font-size: 11px; color: var(--muted); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.part-btn[aria-pressed="true"] { border-color: var(--blue); background: #f1f6ff; }
.part-btn[aria-pressed="true"] .part-btn__name { color: var(--blue); }

/* ---------- text tools ---------- */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr));
  gap: 8px;
  margin: 10px 0;
}
.stats > div {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px;
  background: #fff;
  min-width: 0;
}
.stats dt { font-size: 13px; color: var(--muted); }
.stats dd {
  margin: 2px 0 0;
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--blue);
}

.tool-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

/* The counting screen's own editor. It looks like the shared box upstairs -
   same card, same head row - because it does the same job; it is taller
   because what gets pasted into it is a whole post, not a nickname. */
.text-editor {
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 9px 10px 8px;
  margin-bottom: 10px;
}
#toolsInput {
  width: 100%;
  min-height: 132px;
  padding: 8px 10px;
  font: inherit;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  resize: vertical;
}
#toolsInput:focus { border-color: var(--blue); }
#toolsCount {
  flex: none;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--muted);
}
#toolsCount.is-limit { color: var(--pink); }

/* How each number is counted: collapsed, because it is an answer to a
   question, not something to read before using the screen. */
.tools-basis { margin: 2px 0 12px; }
.tools-basis > summary {
  font-size: 13px;
  font-weight: 600;
  color: var(--blue);
  cursor: pointer;
  padding: 4px 2px;
}
.tools-basis > summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; border-radius: 8px; }
.tools-basis__list {
  margin: 4px 0 0;
  padding-left: 18px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted);
}

/* ---------- length goal ---------- */
.goal {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px;
  background: #fff;
}
.goal h3 { margin: 0 0 6px; font-size: 14px; }
.goal__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
}
.goal__row label { font-size: 14px; font-weight: 600; }
#toolsGoal {
  width: 7em;
  min-height: var(--tap);
  padding: 6px 8px;
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fff;
}
#toolsGoalUnit {
  min-height: var(--tap);
  padding: 6px 8px;
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fff;
  max-width: 100%;
}
#toolsGoal:focus, #toolsGoalUnit:focus { border-color: var(--blue); }
.goal__out {
  margin: 8px 0 0;
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--blue);
}
.goal__out.is-over { color: var(--pink); }
.goal__out.is-error { color: var(--pink); font-weight: 600; }
.goal__bar {
  margin-top: 6px;
  height: 8px;
  border-radius: 999px;
  background: var(--soft);
  overflow: hidden;
}
.goal__bar[hidden] { display: none; }
.goal__bar > span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--blue);
}
.goal__bar > span.is-over { background: var(--pink); }
.goal .note { margin: 8px 0 0; }

/* ---------- in-page FAQ ---------- */
/* Sits below the results, collapsed, so nothing is pushed above the tool. */
.faq {
  margin: 22px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.faq h2 { font-size: 15px; margin: 0 0 2px; }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__item summary {
  padding: 11px 2px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.faq__item summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; border-radius: 8px; }
.faq__item p {
  margin: 0 0 12px;
  padding: 0 2px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted);
}
.faq__more { margin: 10px 0 0; font-size: 13px; font-weight: 600; }

/* ---------- help disclosure ---------- */
.help {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--soft);
  margin: 20px 0 0;
  padding: 0 12px;
}
.help summary {
  padding: 13px 0;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.help summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; }
.help p {
  margin: 0 0 10px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

/* ---------- footer ---------- */
.foot {
  border-top: 1px solid var(--line);
  padding: 16px;
  font-size: 13px;
  color: var(--muted);
  text-align: center;
}
.foot p { margin: 2px 0; }
.foot__links {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0 18px;
  margin: 0 0 6px;
}
.foot__links a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-weight: 600;
}

/* ---------- static pages (guide / about / privacy) ---------- */
.doc { max-width: 760px; padding-top: 14px; }

.doc__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0 16px;
  margin: 0 0 10px;
  font-size: 13px;
  font-weight: 600;
}

.doc__article + .doc__article {
  margin-top: 30px;
  padding-top: 22px;
  border-top: 2px solid var(--line);
}

.doc h1 { font-size: 22px; margin: 0 0 6px; }
.doc h2 { font-size: 17px; margin: 24px 0 6px; }
.doc h3 { font-size: 15px; margin: 16px 0 4px; }
.doc p { margin: 0 0 10px; font-size: 15px; line-height: 1.7; }
.doc ul, .doc ol { margin: 6px 0 12px; padding-left: 22px; }
.doc li { font-size: 15px; line-height: 1.7; margin-bottom: 5px; }
.doc__lead { color: var(--muted); }
/* A file name or an address quoted inline. Wraps rather than widening the page
   on a phone. */
.mono {
  font-family: ui-monospace, Consolas, "D2Coding", "Courier New", monospace;
  font-size: 0.92em;
  overflow-wrap: anywhere;
}
.doc__updated { font-size: 13px; color: var(--muted); margin: 0 0 14px; }
.doc__note { font-size: 13px; line-height: 1.6; color: var(--muted); }

/* Real output, shown at reading size so it can be selected and copied. */
.doc__sample {
  border: 1px solid var(--line);
  border-left: 3px solid var(--pink);
  border-radius: 0 10px 10px 0;
  background: var(--soft);
  padding: 8px 11px;
  margin: 6px 0 12px;
}
.doc__sample b {
  display: block;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  margin-bottom: 2px;
}
.doc__sample span {
  display: block;
  font-size: 17px;
  line-height: 1.7;
  overflow-wrap: anywhere;
}
/* A kit sample is several lines; they have to be shown the way they are
   copied, so the line breaks are kept and only over-long lines wrap. */
.doc__sample span.doc__block {
  white-space: pre-wrap;
  font-size: 16px;
}

.doc__back { margin: 26px 0 0; font-size: 14px; font-weight: 600; }

@media (max-width: 420px) {
  .wrap { padding: 10px 10px 28px; }
  .tool-head h1 { font-size: 17px; }
  .dir-section__title { font-size: 15px; }
  .card__out { font-size: 16px; }
  .card--kit .card__out { font-size: 15px; }
  .face-preview__out { font-size: 26px; }
  .field { flex: 1 1 100%; }
  .field select { min-width: 0; }
  /* The kits tab can show two clear buttons and a caveat at once. Let the
     head stack instead of squeezing the heading into one column. */
  .fav-block { padding: 7px 8px 9px; }
  .fav-block__head { justify-content: flex-start; }
  .fav-block__actions { gap: 4px 6px; }
  /* At 390px the goal row stacks rather than squeezing a number box to
     nothing: the label, the box, then the unit, each on its own line. */
  .goal__row { align-items: stretch; }
  .goal__row label { flex: 1 1 100%; }
  #toolsGoal { width: 100%; }
  #toolsGoalUnit { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .panel { animation: none; }
  /* Asked for less motion, and the page stops: the home samples hold their
     still frame, the hover lift and the card dip are defined only inside a
     no-preference query, and this turns off every transition or animation
     that might have been added since. The script asks the same question
     before it starts a timer, so a still card is not one moving unseen. */
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ======================================================================
   The gallery and the workshop
   ====================================================================== */

/* ---------- the result gallery ----------
   The home screen is a shelf of samples, and nothing stands in front of it:
   no welcome band, no filter chips, no search box. Every card is the block a
   tool really produces, with its name and one small word underneath, so the
   choice is made by looking rather than by reading a list of descriptions.
   Every tool is on the page, under a heading you can see. */

/* A section is a place to pause while scrolling, not a banner: one small
   heading over a thin rule, and the count beside it. The samples underneath
   are what the eye is meant to land on. */
.dir-section { margin: 0 0 18px; }
.dir-section__head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 8px;
  padding: 0 2px 5px;
  border-bottom: 1px solid var(--line);
}
.dir-section__title { position: relative; margin: 0; padding-left: 18px; font-size: 15px; color: var(--ink); }
.dir-section__title:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }
/* A small paper label beside each heading, one colour per shelf, so the five
   sections can be told apart while scrolling past them. Placed rather than
   laid out, which keeps the heading on the same baseline as the count beside
   it - and it is decoration only: the heading still says which section in
   words, and the label is never the only thing that does. */
.dir-section__title::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.24em;
  width: 10px;
  height: 13px;
  border-radius: 3px;
  background: var(--cat-label, var(--wash-lilac));
  border: 1px solid rgba(41, 36, 49, 0.16);
}
.dir-section[data-cat="fonts"] { --cat-label: var(--wash-berry); }
.dir-section[data-cat="symbols"] { --cat-label: var(--wash-lilac); }
.dir-section[data-cat="art"] { --cat-label: var(--wash-mint); }
.dir-section[data-cat="useful"] { --cat-label: var(--wash-butter); }
.dir-section[data-cat="play"] { --cat-label: var(--wash-peach); }
.dir-section__head .chip-note--count { margin-left: auto; color: var(--muted); }

.dir-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr));
  gap: 10px;
}
/* Three across on a laptop, four on a wide window, two at 390px: whatever the
   width, a sample is wide enough to read rather than wide enough to fill. */
@media (min-width: 640px) {
  .dir-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 860px) {
  .dir-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.dir-grid--slim {
  grid-template-columns: repeat(auto-fill, minmax(min(160px, 100%), 1fr));
  gap: 8px;
  margin-top: 4px;
}

/* The star is a separate 44px button, so it is never inside the link - a
   button inside a link is a trap for a mouse and for a keyboard alike. It sits
   in the corner instead of taking a column from a card only 170px wide. */
.dir-card {
  position: relative;
  display: flex;
  align-items: stretch;
  border: 1px solid var(--line);
  border-radius: 19px;
  background: #fff;
  box-shadow: var(--shadow);
  min-width: 0;
}
.dir-card:hover { border-color: var(--pink); }
.dir-card:focus-within { border-color: var(--blue); }
/* The lift is for a pointer only, and only where motion is welcome: on a
   phone a card that moves under a thumb is just a card that moved. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .dir-card { transition: transform 0.14s ease-out, box-shadow 0.14s ease-out; }
  .dir-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
}
/* The pale plate is for an empty star standing over a sample: a starred one
   keeps the bright fill it is recognised by, whatever it is standing on. */
.dir-card > .star-btn {
  position: absolute;
  top: 3px; right: 3px;
  z-index: 1;
}
.dir-card > .star-btn:not([aria-pressed="true"]) { background: rgba(255, 255, 255, 0.92); }

.dir-card__link {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1;
  min-width: 0;
  padding: 8px 10px 10px;
  color: inherit;
  text-decoration: none;
  border-radius: 19px;
}
.dir-card__link:hover .dir-card__name { color: var(--pink); }

/* The sample. A fixed band so a row of cards lines up, and whatever runs over
   is clipped rather than allowed to stretch one card past its neighbours. The
   right padding is the star's corner: the sample is centred in what is left,
   so the button never lands on top of the drawing.

   The height counts in a row above for the drawn character whether or not a
   drawing ever arrives. Booking that room here rather than growing the band
   when a sheet loads is the difference between a page that settles once and a
   page that jumps three times - and between a row of cards that line up and a
   row where the ones whose sheet is still coming stand shorter than their
   neighbours. A sheet that never loads leaves a card taller than it needs to
   be, with its sample in the middle of the room it always had. */
.dir-card__art {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 250px;
  max-height: 250px;
  overflow: hidden;
  padding: 118px 4px 8px;
  color: var(--ink);
  border-radius: 13px;
  /* One thin, definite line round the sample, in a deeper shade of the wash its
     family stands on: what turns a soft area of colour into a card on a shelf.
     It is inside the height the band was already given, so nothing moves. */
  border: 1px solid var(--sticker-edge);
  /* The wash the drawn character brings with it. Without a sheet it is the
     near-white the band has always started from, so this default is what keeps
     the gradient below unchanged until a sticker is really there. */
  --sticker-wash: #fffdfc;
  --sticker-edge: #f0e3ea;
  /* One pale surface behind every sample, warm at the foot and near-white at
     the top. The sample itself stays dark ink: the colour is behind it. */
  background: linear-gradient(180deg, #fffdfc 0%, var(--soft) 100%);
}
.dir-card__art--pair { flex-direction: column; align-items: center; gap: 1px; }

/* Art made of characters keeps its own columns, so it needs a fixed pitch. */
.dir-art__lines,
.dir-art__to {
  font-family: ui-monospace, Consolas, "D2Coding", "Courier New", monospace;
  font-size: 14px;
  line-height: 1.32;
  white-space: pre;
  text-align: left;
}
.dir-art__from {
  font-size: 13px;
  color: var(--muted);
  white-space: pre;
}
.dir-art__to { font-size: 20px; }
.dir-art__arrow { font-size: 12px; color: var(--muted); line-height: 1; }
/* The picture takes half the row here, so the dots step down to fit beside it. */
.dir-card__art--photo .dir-art__to { font-size: 13px; }

/* ---------- the drawn character ----------
   Thirty-three hand-drawn characters, one to a tool, live in three transparent
   sheets of twelve. A card shows its own drawing by scaling the whole sheet
   behind this little window and sliding that drawing into it, so twelve cards
   are painted from one file and no sheet is ever cut up.

   The window is not a twelfth of the sheet. The sheets are painted as posters -
   the drawings sit at uneven distances and are not the same size or shape - so
   each card is told the rectangle its own drawing really occupies. The six
   numbers below every card's name are that rectangle and the sheet around it,
   each divided by the drawing's longest side, so one `--fit` decides how big a
   drawing is and everything else follows: they are ratios, and a sheet
   re-encoded at another size needs no change here. scripts/build-stickers.mjs
   works them out from the artist's box list and prints the block.

   Three attributes on the band, three questions: `data-sticker` is which of the
   thirty-three drawings, `data-sticker-sheet` is which file it is on, and
   `data-sticker-family` is which of the six washes it stands on. They are
   separate because a drawing is one to a tool and a wash is one to a kind of
   work, and neither should drag the other about.

   It is a decoration and it behaves like one: no text, out of the flow, no
   pointer, and hidden from a screen reader twice over (the band around it
   already carries aria-hidden). It stands in a fixed slot above the sample -
   booked whether or not a sheet ever loads - and the star keeps its corner.
   Since the drawing is centred, half of it grows towards that corner: it is
   shifted 6px back from the middle, and on a card too narrow to hold the full
   size the window shrinks with the card rather than sliding under the button.

   What waits for `has-stickers-a` (or -b, or -c), which card-stickers.js writes
   once that sheet has really loaded, is only the drawing itself and the wash
   behind it. A file that is missing, blocked or not drawn yet leaves its own
   twelve cards on the same band, with the same sample in the same place. */
.dir-sticker {
  display: none;
  position: absolute;
  /* The slot the drawing stands in: 104px tall, 8px below the top of the band,
     and the sample starts 6px under it. */
  top: 8px;
  bottom: 138px;
  left: 0;
  /* Centred in that slot, six pixels back from the middle: the star stands in
     the corner the other way, and half of a centred drawing grows towards it.
     Centred with automatic margins rather than a transform - the transform
     property has to stay free for the rock below, and a centring transform
     would be the one the rock overwrites. */
  right: 12px;
  margin: auto;
  --frame: 104px;
  /* The drawing fills the slot but for a little air, which is what keeps the
     star clear of it even at the far end of the rock: a drawing turned three
     degrees swings its top corner about four more pixels out. */
  --fit: calc(var(--frame) * 0.93);
  /* A square drawing on a sheet of three by four, until a rule below says which
     drawing this really is. */
  --bw: 1;
  --bh: 1;
  --px: 0;
  --py: 0;
  --sw: 3;
  --sh: 4;
  width: calc(var(--bw) * var(--fit));
  height: calc(var(--bh) * var(--fit));
  pointer-events: none;
  /* It rocks on its feet rather than about its middle, the way a paper sticker
     peeling off a page would. */
  transform-origin: 50% 88%;
  background-repeat: no-repeat;
  background-size: calc(var(--sw) * var(--fit)) calc(var(--sh) * var(--fit));
  background-position: calc(var(--px) * var(--fit) * -1) calc(var(--py) * var(--fit) * -1);
}
.has-stickers-a & .dir-card__art[data-sticker-sheet="a"] .dir-sticker,
.has-stickers-b & .dir-card__art[data-sticker-sheet="b"] .dir-sticker,
.has-stickers-c & .dir-card__art[data-sticker-sheet="c"] .dir-sticker { display: block; }

/* Which file. Three requests paint the gallery. */
.dir-card__art[data-sticker-sheet="a"] .dir-sticker { background-image: url("assets/glypop-unique-a-v2.webp"); }
.dir-card__art[data-sticker-sheet="b"] .dir-sticker { background-image: url("assets/glypop-unique-b-v2.webp"); }
.dir-card__art[data-sticker-sheet="c"] .dir-sticker { background-image: url("assets/glypop-unique-c-v2.webp"); }

/* Which drawing, and where it sits on its sheet: the rectangle it occupies
   and the sheet around it, both divided by its longest side. Written by
   scripts/build-stickers.mjs from the artist's box list - the test holds this
   block and that list to each other rather than trusting either alone. */
.dir-card__art[data-sticker="fancy"] .dir-sticker { --bw: 0.84281; --bh: 1; --px: 0.26756; --py: 0.19398; --sw: 3.63211; --sh: 4.84281; }
.dir-card__art[data-sticker="decorate"] .dir-sticker { --bw: 1; --bh: 0.70948; --px: 1.16208; --py: 0.30581; --sw: 3.3211; --sh: 4.42813; }
.dir-card__art[data-sticker="kits"] .dir-sticker { --bw: 0.94464; --bh: 1; --px: 2.6436; --py: 0.23183; --sw: 3.75779; --sh: 5.01038; }
.dir-card__art[data-sticker="effects"] .dir-sticker { --bw: 1; --bh: 0.93493; --px: 0.17466; --py: 1.40411; --sw: 3.71918; --sh: 4.9589; }
.dir-card__art[data-sticker="zalgo"] .dir-sticker { --bw: 0.77256; --bh: 1; --px: 1.6065; --py: 1.4657; --sw: 3.92058; --sh: 5.22744; }
.dir-card__art[data-sticker="kfancy"] .dir-sticker { --bw: 1; --bh: 0.62102; --px: 2.32803; --py: 1.47452; --sw: 3.4586; --sh: 4.61146; }
.dir-card__art[data-sticker="bigtext"] .dir-sticker { --bw: 1; --bh: 0.8109; --px: 0.14423; --py: 2.45192; --sw: 3.48077; --sh: 4.64103; }
.dir-card__art[data-sticker="banner"] .dir-sticker { --bw: 1; --bh: 0.90635; --px: 1.31773; --py: 2.50167; --sw: 3.63211; --sh: 4.84281; }
.dir-card__art[data-sticker="widthconv"] .dir-sticker { --bw: 1; --bh: 0.53968; --px: 2.32381; --py: 2.53016; --sw: 3.44762; --sh: 4.59683; }
.dir-card__art[data-sticker="heta"] .dir-sticker { --bw: 1; --bh: 0.99365; --px: 0.15238; --py: 3.38095; --sw: 3.44762; --sh: 4.59683; }
.dir-card__art[data-sticker="symbols"] .dir-sticker { --bw: 1; --bh: 0.93377; --px: 1.29801; --py: 3.58609; --sw: 3.59603; --sh: 4.7947; }
.dir-card__art[data-sticker="faces"] .dir-sticker { --bw: 1; --bh: 0.88088; --px: 2.30721; --py: 3.39498; --sw: 3.40439; --sh: 4.53918; }
.dir-card__art[data-sticker="catalog"] .dir-sticker { --bw: 1; --bh: 0.95652; --px: 0.16722; --py: 0.21405; --sw: 3.63211; --sh: 4.84281; }
.dir-card__art[data-sticker="emoji"] .dir-sticker { --bw: 1; --bh: 0.98208; --px: 1.46595; --py: 0.26165; --sw: 3.89247; --sh: 5.18996; }
.dir-card__art[data-sticker="egypt"] .dir-sticker { --bw: 1; --bh: 0.91749; --px: 2.43894; --py: 0.25083; --sw: 3.58416; --sh: 4.77888; }
.dir-card__art[data-sticker="emoticons"] .dir-sticker { --bw: 1; --bh: 0.97872; --px: 0.17021; --py: 1.49645; --sw: 3.85106; --sh: 5.13475; }
.dir-card__art[data-sticker="unicode"] .dir-sticker { --bw: 1; --bh: 0.89937; --px: 1.21384; --py: 1.31132; --sw: 3.41509; --sh: 4.55346; }
.dir-card__art[data-sticker="limb"] .dir-sticker { --bw: 0.92208; --bh: 1; --px: 2.46104; --py: 1.2987; --sw: 3.52597; --sh: 4.7013; }
.dir-card__art[data-sticker="extract"] .dir-sticker { --bw: 0.96285; --bh: 1; --px: 0.10526; --py: 2.26625; --sw: 3.36223; --sh: 4.48297; }
.dir-card__art[data-sticker="asciiart"] .dir-sticker { --bw: 1; --bh: 0.82716; --px: 1.20062; --py: 2.36728; --sw: 3.35185; --sh: 4.46914; }
.dir-card__art[data-sticker="dotart"] .dir-sticker { --bw: 0.95793; --bh: 1; --px: 2.45955; --py: 2.39482; --sw: 3.51456; --sh: 4.68608; }
.dir-card__art[data-sticker="doteditor"] .dir-sticker { --bw: 0.95946; --bh: 1; --px: 0.14527; --py: 3.68919; --sw: 3.66892; --sh: 4.89189; }
.dir-card__art[data-sticker="emojiart"] .dir-sticker { --bw: 1; --bh: 0.89971; --px: 1.10324; --py: 3.19469; --sw: 3.20354; --sh: 4.27139; }
.dir-card__art[data-sticker="tools"] .dir-sticker { --bw: 1; --bh: 0.97611; --px: 2.5802; --py: 3.72696; --sw: 3.70648; --sh: 4.94198; }
.dir-card__art[data-sticker="calendar"] .dir-sticker { --bw: 1; --bh: 0.84177; --px: 0.12658; --py: 0.24051; --sw: 3.43671; --sh: 4.58228; }
.dir-card__art[data-sticker="morse"] .dir-sticker { --bw: 1; --bh: 0.8109; --px: 1.24679; --py: 0.28205; --sw: 3.48077; --sh: 4.64103; }
.dir-card__art[data-sticker="secret"] .dir-sticker { --bw: 1; --bh: 0.88339; --px: 2.68198; --py: 0.32155; --sw: 3.83746; --sh: 5.11661; }
.dir-card__art[data-sticker="linebreak"] .dir-sticker { --bw: 0.91919; --bh: 1; --px: 0.17508; --py: 1.37374; --sw: 3.65657; --sh: 4.87542; }
.dir-card__art[data-sticker="hashtag"] .dir-sticker { --bw: 1; --bh: 0.86731; --px: 1.27508; --py: 1.38835; --sw: 3.51456; --sh: 4.68608; }
.dir-card__art[data-sticker="themes"] .dir-sticker { --bw: 0.86014; --bh: 1; --px: 2.73077; --py: 1.42657; --sw: 3.7972; --sh: 5.06294; }
.dir-card__art[data-sticker="loud"] .dir-sticker { --bw: 1; --bh: 0.88182; --px: 0.14848; --py: 2.27879; --sw: 3.29091; --sh: 4.38788; }
.dir-card__art[data-sticker="quiz"] .dir-sticker { --bw: 1; --bh: 0.96246; --px: 1.45392; --py: 2.59044; --sw: 3.70648; --sh: 4.94198; }
.dir-card__art[data-sticker="fortune"] .dir-sticker { --bw: 0.9026; --bh: 1; --px: 2.47403; --py: 2.40584; --sw: 3.52597; --sh: 4.7013; }

/* One pale wash per kind of work - the five the palette already holds plus one
   mint. Thirty-three drawings and six colours: the picture is what tells two
   cards apart, the colour is what tells two shelves apart. It is the top of the
   band only; the sample still sits on near-white and is still plain dark ink. */
.dir-card__art[data-sticker-family="letters"] { --sticker-wash: var(--wash-berry); }
.dir-card__art[data-sticker-family="play"] { --sticker-wash: var(--wash-peach); }
.dir-card__art[data-sticker-family="wrap"] { --sticker-wash: var(--wash-petal); }
.dir-card__art[data-sticker-family="picture"] { --sticker-wash: var(--wash-mint); }
.dir-card__art[data-sticker-family="catalogue"] { --sticker-wash: var(--wash-lilac); }
.dir-card__art[data-sticker-family="message"] { --sticker-wash: var(--wash-butter); }

/* And the line round the band: the same six colours taken a few steps darker,
   so the edge is visible against the paper without becoming a second colour.
   Written apart from the washes above because the wash arrives with a sheet
   and the line does not - a card whose drawing is still coming is already the
   right card on the right shelf. */
.dir-card__art[data-sticker-family="letters"] { --sticker-edge: #f2c5d3; }
.dir-card__art[data-sticker-family="play"] { --sticker-edge: #f4d2ba; }
.dir-card__art[data-sticker-family="wrap"] { --sticker-edge: #ebcde3; }
.dir-card__art[data-sticker-family="picture"] { --sticker-edge: #c3e2cd; }
.dir-card__art[data-sticker-family="catalogue"] { --sticker-edge: #ddd1f3; }
.dir-card__art[data-sticker-family="message"] { --sticker-edge: #eedcaf; }

/* The wash arrives with the drawing and not before: a card whose sheet is still
   coming keeps the plain near-white band it was built with. Nothing here moves
   anything - the height was booked up front, so this is a repaint and not a
   reflow. */
.has-stickers-a & .dir-card__art[data-sticker-sheet="a"],
.has-stickers-b & .dir-card__art[data-sticker-sheet="b"],
.has-stickers-c & .dir-card__art[data-sticker-sheet="c"] {
  background: linear-gradient(180deg, var(--sticker-wash) 0%, #fffdfc 46%, var(--soft) 100%);
}

/* ---------- the sample that moves ----------
   A card shows what its tool makes; moving, it shows the tool making it. The
   frames are written by card-motion.js out of the sample the card already
   holds, so a moving card and a still one carry the same characters and the
   same number of lines - the band never resizes and the grid never reflows.

   Everything below sits inside a no-preference query, and the script asks the
   same question before it starts a single timer. */

/* The part of an output that has not been reached yet. It is still there, and
   still exactly as wide: only the ink is pale, so nothing moves as it fills. */
.dir-art__pending { opacity: 0.24; }

@media (prefers-reduced-motion: no-preference) {
  .dir-card__art { transition: transform 0.18s ease-out; }
  /* Hovered, tapped or reached by the keyboard: one small answer back, and
     the sample skips to its next frame rather than waiting out the hold. */
  .dir-card__art.is-poked { transform: scale(1.03); }

  /* Samples made of pictures - emoji, hieroglyphs, a drawn cat - say nothing
     new if their characters change, so those cards dip instead. The animation
     is on the contents, never on the band: the background stays put. The drawn
     character is left out of it - it has a rock of its own below, and a node
     cannot run two. */
  .dir-card__art--bounce.is-live > :not(.dir-sticker) { animation: dirBounce 3.6s ease-in-out infinite; }
  /* Neighbours never dip together. */
  .dir-grid .dir-card:nth-child(3n + 2) .dir-card__art--bounce.is-live > :not(.dir-sticker) { animation-delay: 1.2s; }
  .dir-grid .dir-card:nth-child(3n + 3) .dir-card__art--bounce.is-live > :not(.dir-sticker) { animation-delay: 2.4s; }

  /* The character rocks on the same terms as everything else here: `is-live` is
     written by card-motion.js only for a card that is on the screen, in a tab
     that is being looked at - so this needs no timer, no observer and no second
     opinion about reduced motion. Most of the cycle is stillness. */
  .dir-card__art.is-live .dir-sticker { animation: dirStickerRock 4.4s ease-in-out infinite; }
  .dir-grid .dir-card:nth-child(3n + 2) .dir-card__art.is-live .dir-sticker { animation-delay: 1.5s; }
  .dir-grid .dir-card:nth-child(3n + 3) .dir-card__art.is-live .dir-sticker { animation-delay: 3s; }
}

/* Most of the cycle is rest: one dip, one smaller one, then nothing for three
   seconds. A shelf of cards has to be readable, not busy. */


/* A lean one way, a lean back, and then it stands still for three seconds. Four
   degrees is enough to read as alive on a 78px drawing and small enough that a
   shelf of them is not a shelf of flapping. */


/* The photo tool needs something to convert, so the card draws one: sky, sun
   and a hill, all in CSS. No image file, and nothing fetched to paint it. */
.dir-photo {
  position: relative;
  flex: none;
  width: 48px;
  height: 38px;
  border-radius: 5px;
  border: 1px solid #c8d4e4;
  overflow: hidden;
  background: linear-gradient(#cfe4fb 0%, #e8f3ff 54%, #cfe9d4 54%, #a9d3b4 100%);
}
.dir-photo::before {
  content: "";
  position: absolute;
  top: 5px; right: 6px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #f5b841;
}
.dir-photo::after {
  content: "";
  position: absolute;
  left: 7px; bottom: -6px;
  width: 0; height: 0;
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
  border-bottom: 24px solid #7d97b8;
}

/* Under the sample: the name, and one small word that says the card opens
   something. Nothing else - the sentence about what a tool is for is on the
   tool's own screen, where it is needed. */
.dir-card__foot {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  /* A hairline under the sample and the name sitting just below it: the name
     area is a strip of its own rather than words left loose at the foot of the
     card. The band gave back the room this takes, so the card is the height it
     always was. */
  padding-top: 5px;
  border-top: 1px solid var(--line);
}
.dir-card__name {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.dir-card__open { flex: none; font-size: 13px; font-weight: 600; color: var(--pink); }

/* The recent and starred rows: a signpost back to something already known, so
   one capped line of sample is enough and the row stays short. No sample band
   here - that is what keeps a repeat visit from pushing the gallery off the
   screen. */
/* Plain white paper on a tinted shelf, in both saved rows: the colour belongs
   to the shelf, and the cards standing on it are the same card either way. */
.dir-card--slim {
  border-radius: 15px;
  box-shadow: none;
}
.dir-card--slim > .star-btn:not([aria-pressed="true"]) { background: rgba(255, 255, 255, 0.86); }
.dir-card--slim .dir-card__link { padding: 8px 46px 8px 11px; border-radius: 15px; }
.dir-card--slim .dir-card__name { font-size: 14px; }
.dir-card--slim .dir-card__preview {
  font-size: 14px;
  line-height: 1.3;
  max-height: 1.3em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
}

/* The same compact panel as the starred block, so the two saved rows read as
   a pair and neither one grows a band of its own. */
.recent {
  margin: 0 0 10px;
  padding: 7px 10px 8px;
  border: 1px solid #f5ddd0;
  border-radius: 16px;
  background: linear-gradient(180deg, var(--wash-peach) 0%, #fffdfb 58%);
}
.recent[hidden] { display: none; }
/* What is kept and what is not, in one quiet line under the row. Kept at 14px:
   the block gets its height back from the shelf above, not from small type. */
.recent .chip-note { margin: 2px 0 0; font-size: 14px; }

/* ---------- the recent shelf ----------
   Six recently opened tools used to stand two across and three rows deep, which
   on a phone is most of the first screen spent on where you have already been.
   They lie in one row now and it scrolls sideways: every one of them is still
   reachable, by thumb or by Tab, and a card cut off at the right edge is what
   says there is more of the row over there.

   The row is a flex line, so the slim grid's columns above no longer apply -
   a card is as wide as it is told to be here, not a share of the screen. */
.dir-grid--shelf {
  display: flex;
  gap: 8px;
  margin: 2px -4px 0;
  /* A sideways scroller clips in both directions, so the focus ring, the hover
     lift and the card's own shadow are given room inside it rather than cut
     off. The negative margin above gives that room back to the block, so the
     first card still starts where the heading does. */
  padding: 5px 4px 7px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-left: 4px;
  scrollbar-width: thin;
}
.dir-grid--shelf > .dir-card {
  flex: none;
  width: 170px;
  /* The star inside already stands 44px tall; this is the promise rather than
     the measurement, for a card whose name and sample both come up short. */
  min-height: var(--tap);
  scroll-snap-align: start;
}
/* On a window with room for all six, they share the row instead of running off
   the end of it: the sideways scroll is what a narrow screen needs, not a shape
   the shelf has to keep everywhere. */
@media (min-width: 640px) {
  .dir-grid--shelf > .dir-card { flex: 1 1 0; width: auto; min-width: 128px; max-width: 220px; }
}

/* ---------- workshop chrome ---------- */
.ws-back { margin: 0 0 8px; font-size: 14px; font-weight: 600; }
.ws-head__row { display: flex; align-items: center; gap: 8px; }
.ws-head__row h1 { font-size: 19px; margin: 0; flex: 1; min-width: 0; }
.related { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 12px; }
.related[hidden] { display: none; }
.related h2 { margin-bottom: 6px; }

/* A signpost, not a showcase: the name reads first, and the sample is one
   capped grey line under it. Left as a wide sample it produced slabs of block
   characters with a small blue label wrapped around them on a phone. */
.related-link {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  color: inherit;
  text-decoration: none;
}
.related-link:hover { border-color: var(--pink); }
.related-link:hover .related-link__name { color: var(--pink); }
.related-link:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
.related-link__name { font-size: 15px; font-weight: 700; overflow-wrap: anywhere; }
.related-link__preview {
  font-size: 13px;
  line-height: 1.3;
  max-height: 1.3em;
  color: var(--muted);
  /* One line: the previews carry newlines, and `nowrap` turns those into
     spaces instead of a three-line block. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- shared workshop pieces ---------- */
.card--wide { margin: 8px 0; }
/* Art, banners and calendars only line up in a fixed-width font, and they
   scroll sideways rather than being rewrapped into nonsense. */
.card__out--art {
  font-family: ui-monospace, Consolas, "D2Coding", "Courier New", monospace;
  font-size: 13px;
  line-height: 1.15;
  white-space: pre;
  overflow-x: auto;
}
/* Small drawings, set larger and centred.
   One size for every drawing meant a seven-column cat was a speck against the
   left edge of a card wide enough for a thirty-column divider. The narrower a
   drawing is, the larger it is set; `width: fit-content` with auto margins
   centres the block as a whole rather than centring each line, which would
   pull a picture apart. `max-width` keeps every size inside the card, so
   nothing is ever cut off, and only the widest drawings - which are left at
   the size above - still scroll sideways. None of this touches the string:
   what the copy button sends is the drawing exactly as it is stored. */
.card__out--art-md,
.card__out--art-lg,
.card__out--art-xl {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
}
.card__out--art-md { font-size: 16px; }
.card__out--art-lg { font-size: 20px; }
.card__out--art-xl { font-size: 26px; }

.note--warn { color: var(--pink); font-weight: 600; }
.note--warn[hidden] { display: none; }
.note--ok { color: var(--blue); font-weight: 600; }

.pager {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 8px 0 4px;
}
.pager button[disabled] { opacity: 0.45; cursor: default; }

/* The face library is forty-three pages long, so under the grid there is a
   way to go straight to one. It sits below "show more", not above the faces,
   and at 390px the two halves stack instead of scrolling sideways. */
.face-jump {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 2px 0 14px;
}
.face-jump .pager { margin: 0; }

.face-jump__go {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 0;
}

/* A glyph with its code point underneath. */
.symbol-btn--tall {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
}
.symbol-btn__glyph { font-size: 20px; line-height: 1.1; }
.symbol-btn__code { font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The face library. A finished face is the thing being chosen, so each one
   gets a card and is set large enough to actually read - a wall of 15px
   buttons made six hundred faces into six hundred smudges. */
.face-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(168px, 100%), 1fr));
  gap: 8px;
  align-items: stretch;
}
.face-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 104px;
  padding: 12px 10px 10px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 16px;
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow);
}
/* A long face gets two columns rather than being folded in half. At 390px the
   grid is two columns wide, so it takes the whole row. */
.face-btn--wide { grid-column: span 2; }
.face-btn__face {
  max-width: 100%;
  font-size: 26px;
  line-height: 1.4;
  /* Kept as written, and allowed to fold only if it truly cannot fit - which
     is what keeps the tallest faces from being cut off at the card edge. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* The whole card copies, and it says so in the same place on every card, so
   the word never moves under a thumb that is already reaching for it. */
.face-btn__copy {
  flex: none;
  padding: 2px 11px;
  font-size: 12px;
  font-weight: 700;
  color: var(--pink);
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.face-btn:hover { border-color: var(--pink); }
.face-btn:hover .face-btn__copy { background: var(--wash-berry); border-color: var(--pink); }

/* ---------- Unicode inspector ---------- */
.inspect-list { display: flex; flex-direction: column; gap: 4px; }
.inspect-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 4px 8px;
}
.inspect-row__glyph { font-size: 20px; min-width: 28px; text-align: center; }
.inspect-row__codes {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* ---------- dot editor ---------- */
/* The wrapper scrolls, not the page: at 44px a 16-wide grid is 720px, which is
   wider than a phone, and a whole page that slides sideways is much worse than
   one box that does. */
.dot-grid-wrap {
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  margin-bottom: 10px;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
.dot-grid-wrap--full { border-radius: var(--radius); }

.dot-grid {
  display: inline-flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--soft);
}
.dot-grid__row { display: flex; gap: 1px; }
.dot-cell {
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid #dfe3ee;
  border-radius: 3px;
  background: #fff;
  cursor: pointer;
}
/* The accessible size: the same 44px target every other control on the site
   uses, for anyone who cannot hit a 20px square. */
.dot-grid--full .dot-cell { width: 44px; height: 44px; border-radius: 5px; }
.dot-cell[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); }
.dot-cell:hover { border-color: var(--pink); }
.dot-cell:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }

/* ---------- Morse table ---------- */
.morse-table {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 4px 10px;
  margin-top: 6px;
}
.morse-table__cell { display: flex; gap: 6px; font-size: 13px; font-variant-numeric: tabular-nums; }
.morse-table__cell b { min-width: 14px; }

/* ---------- quiz ---------- */
.quiz { display: flex; flex-direction: column; gap: 6px; }
.quiz__emoji { font-size: 34px; line-height: 1.2; margin: 4px 0; letter-spacing: 4px; }
.quiz__done { font-size: 17px; font-weight: 700; margin: 8px 0; }

/* ---------- emoji pictures ---------- */
/* The whole palette folds away, so the gallery of pictures sits close under
   the preview instead of below a screenful of swatches. The summary is the
   control: 44px tall like every other tap target here. */
.palette-fold { margin: 8px 0; }
.palette-fold__summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 12px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
.palette-fold[open] .palette-fold__summary { margin-bottom: 4px; }

/* ---------- a panel's explanation, folded ---------- */
/* Same shell as the palette fold, used at the top of a panel whose paragraphs
   would otherwise stand between a 390px screen and the first result. Closed it
   is one 44px line; the words are still in the page and still read aloud. */
.panel-fold { margin: 0 0 8px; }
.panel-fold__summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 12px;
  font-size: 15px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.panel-fold__summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; }
.panel-fold[open] .panel-fold__summary { margin-bottom: 4px; }
.panel-fold > .note { margin: 4px 0 0; }

/* One row per part of the drawing: what the part is, what it is set to now,
   the colours on offer, and a box for anything else. */
.palette-row {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 6px 8px 8px;
  margin: 6px 0;
}
.palette-row__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.palette-row__name { font-size: 14px; font-weight: 600; }
.palette-row__now { font-size: 20px; line-height: 1.1; }
.palette-swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: 4px;
  margin: 6px 0;
}
.palette-swatches .symbol-btn[aria-pressed="true"] {
  border-color: var(--pink);
  background: var(--wash-berry);
}

.art-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
  gap: 8px;
}
.art-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-height: var(--tap);
  padding: 8px 6px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 12px;
  color: var(--ink);
  cursor: pointer;
}
.art-btn:hover { border-color: var(--pink); background: var(--wash-berry); }
.art-btn[aria-pressed="true"] { border-color: var(--pink); background: var(--wash-berry); }
/* The sample is a picture, so it keeps its own line breaks and scrolls
   sideways rather than being rewrapped into something unrecognisable. */
.art-btn__art {
  display: block;
  margin: 0;
  font-size: 13px;
  line-height: 1.05;
  white-space: pre;
  overflow-x: auto;
  max-width: 100%;
}
.art-btn__name { font-size: 13px; color: var(--muted); }

/* ---------- themed snippets ---------- */
.snippet-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 6px 8px;
  margin: 4px 0;
}
.snippet-row__tick { flex: 0 0 auto; }
/* A snippet can be three lines, and the line breaks are part of what is being
   copied, so the preview keeps them. */
.snippet-row__text {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 14px;
  line-height: 1.35;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Range, file and checkbox inputs sit in the same field box as the rest. */
.field input[type="range"] { padding: 0; height: var(--tap); background: none; border: 0; }
.field input[type="file"] { padding: 8px; font-size: 14px; }
.field input[type="checkbox"] { width: 22px; height: 22px; }

@media (max-width: 420px) {
  /* Two columns at 390px, not one: a single column turns the directory into a
     very long scroll, and two still leave ~170px - enough for a readable title and
     a sample worth looking at once the sample text steps down a size. */
  .dir-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .dir-grid--slim { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Two cards and a slice of a third at 390px: the slice is the whole point,
     and a narrower card would cut the names it is there to show. */
  .dir-grid--shelf > .dir-card { width: 150px; }
  /* Four or five pixels off each gap above the gallery, which together are the
     difference between the first row of tools being on the first screen and
     just under it. */
  .recent { margin-bottom: 8px; padding: 6px 8px 7px; }
  .fav-hint { margin-bottom: 4px; }
  .dir-section { margin-bottom: 14px; }
  .dir-card__link { padding: 7px 8px 9px; }
  /* The band shrinks instead of the words. Five lines of sample at 12px need
     about 125px inside the padding, so the 128px under the drawing's slot still
     holds the tallest sample - and what the band gives back pays for the name
     and purpose below staying at a size worth reading. */
  .dir-card__art { min-height: 230px; max-height: 230px; gap: 4px; padding: 102px 3px 6px; }
  /* The drawing steps down with the card, and one line moves the window, the
     sheet behind it and every offset together. Two columns of a narrow screen
     come to half of it less the page padding, the gap, the card border and the
     room the star needs beside the drawing - which is 87px at 390px and less on
     a smaller phone, rather than a drawing sliding under the button. */
  .dir-sticker { top: 6px; bottom: 134px; --frame: min(90px, calc(50vw - 108px)); }
  .dir-art__lines { font-size: 12px; line-height: 1.28; }
  .dir-art__to { font-size: 16px; line-height: 1.28; }
  .dir-art__from { font-size: 12px; }
  /* The name keeps its desktop size here on purpose: this is the screen where
     the gallery is hardest to scan, so it is the last place to shrink the one
     line a tool is named by. */
  .dir-card__name { font-size: 15px; }
  .dir-card__open { font-size: 13px; }
  .dir-photo { width: 38px; height: 30px; }
  .dir-photo::before { width: 8px; height: 8px; top: 4px; right: 5px; }
  .dir-photo::after { border-left-width: 12px; border-right-width: 12px; border-bottom-width: 19px; }
  .card__out--art { font-size: 11px; }
  /* Still larger than the base size, still inside a 390px card: the widest of
     these is 26 columns at 14px, which is about 220px of monospace. */
  .card__out--art-md { font-size: 14px; }
  .card__out--art-lg { font-size: 17px; }
  .card__out--art-xl { font-size: 21px; }
  /* Two face cards across at 390px, still large enough to read. */
  .face-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
  .face-btn { min-height: 92px; padding: 10px 7px 9px; gap: 7px; }
  .face-btn__face { font-size: 22px; }
  .art-grid { grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); }
  .art-btn__art { font-size: 11px; }
  .snippet-row__text { font-size: 13px; }
  .dot-cell { width: 17px; height: 17px; }
  /* The large size stays 44px on a phone: shrinking the accessible option on
     the screens that need it most would defeat the point. */
  .dot-grid--full .dot-cell { width: 44px; height: 44px; }
  .quiz__emoji { font-size: 28px; }
}

/* tools-hub design alignment: white page, navy ink, the shared teal as the accent. The pastel
   washes behind samples stay (they keep a wall of decorated text readable). Error and over-limit
   states that used the accent get the shared red back, so a warning never reads as a highlight. */
& { display: block; padding-top: 28px; --bg: #ffffff; --ink: #0b1f3a; --pink: #0b6169; --blue: #0b6169; --edge-berry: #0b6169; background: #ffffff; }
.status.is-error, #inputCount.is-limit, #toolsCount.is-limit, .goal__out.is-over, .goal__out.is-error { color: #b3261e; }
.goal__bar > span.is-over { background: #b3261e; }
.wrap { max-width: none; padding-left: 0; padding-right: 0; }
.hub-hidden { display: none !important; }
/* tools-hub focused tool view: the shared one-line bar holds the way back and the page or tool name
   (hub-lang-link.js copies the tool's h1 into it), so the app's own back links go and its tool h1 stays
   for screen readers only. Purpose lines, the ☆ button and all controls stay. */
.tool-view & { padding-top: 12px; }
.tool-view & .tool-head__back, .tool-view & .ws-back { display: none; }
.tool-view & .tool-head h1, .tool-view & .ws-head h1 {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.tool-view & .tool-head .note { margin-top: 0; }
}
