/* =========================================================================
   SYSTEM - reset, base typography, the shared shell and the reusable
   components. Loaded by every page, right after tokens.css and before the
   page file. Anything used by 2+ pages lives here; single-page styles live
   in that page's own file.

   Sections: BASE / LAYOUT (shell) / COMPONENTS / FOCUS-VISIBLE / RESPONSIVE
   ========================================================================= */


/* --------------------------------------------------------------------------
   BASE
   Note: html/body height, overflow and display are NOT set here. Pages that
   need a locked, non-scrolling viewport (designer, landing) opt in from their
   own file; everything else keeps the normal document flow.
   -------------------------------------------------------------------------- */

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

body {
  font-family: var(--body);
  color: var(--ink);
  background: var(--paper);
  font-size: 14px;
  line-height: 1.5;
  background-image:
    radial-gradient(ellipse at 12% 8%, rgba(184,138,90,.06) 0%, transparent 40%),
    radial-gradient(ellipse at 88% 92%, rgba(184,138,90,.05) 0%, transparent 45%);
}

/* Lucide renders <i data-lucide> as a 24px <svg>; inline icons inside a button
   or label track the text size instead, so a label never shifts when the CDN
   resolves. Components that need a fixed icon size override this locally. */
.btn svg, .badge svg, .chip svg { width: 1.05em; height: 1.05em; flex-shrink: 0; }


/* --------------------------------------------------------------------------
   LAYOUT: topbar
   -------------------------------------------------------------------------- */

.topbar {
  height: var(--topbar-height);
  background: var(--paper);
  border-bottom: 1px solid var(--line-soft);
  padding: 0 var(--space-xl);
  display: flex; align-items: center; gap: var(--space-xl);
  flex-shrink: 0;
  z-index: var(--z-topbar);
  box-shadow: var(--shadow);
}
/* REMOVED: the pinned-bar modifier. Its only consumer was the landing while that
   page's body was a 520vh scroll track — a static bar would have left with the first
   wheel notch and never come back. The landing is one viewport now, so there is
   nothing to scroll past and the rule had no callers.

   Worth recording because it did NOT show up as dead: `test_no_orphan_css_classes`
   builds its "referenced" set from the raw HTML/JS text, comments included, so a
   comment in landing.html explaining why the page no longer uses the class was
   enough to keep the rule looking alive. If a future page needs a pinned bar, add
   the modifier back here (a variant of a shared component belongs with it, like
   `.doc-topbar`) rather than restating the bar's z-index and geometry in a page file.

   `--topbar-height` is unaffected and still equals the bar's real height at every
   width — see the responsive block at the end of this file. */
.brand {
  display: flex; align-items: center; gap: .55rem;
  text-decoration: none; color: var(--ink);
  height: 100%; padding: 8px 0;
}
.brand img { height: 60px; width: auto; object-fit: contain; }

/* Section nav: the active page is underlined rather than boxed, so the topbar
   stays a quiet band and the page content leads. */
.sectnav { display: flex; gap: var(--space-xl); margin-left: auto; }
.sectnav a {
  position: relative;
  text-decoration: none; color: var(--ink-soft);
  padding: .4rem 0;
  font-family: var(--body); font-size: var(--text-lg); font-weight: 500;
  transition: color var(--dur-fast);
}
.sectnav a:hover { color: var(--cocoa-dk); }
.sectnav a.active { color: var(--ink); font-weight: 700; }
.sectnav a.active::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; border-radius: 2px;
  background: var(--ink);
}
/* The nav's own `margin-left: auto` is what right-aligns the whole cluster (nav
   + icons). There is deliberately NO flex:1 spacer between them:
   a spacer AFTER the nav would absorb the free space and push the links back
   left, against the brand - which is exactly the bug it used to cause.

   The right side is TWO groups, not one: places you go (the section nav) and
   controls that act on where you already are (help, language, account). It used
   to be pulled in by a negative --space-lg so the whole thing read as one compact
   block; the divider below states the seam instead, so the pull-in is gone and the
   gap around the rule is what carries the grouping. */
.topbar-icons {
  display: flex; align-items: center; gap: var(--space-md);
}
/* When the icons come STRAIGHT after the brand there is no nav to do the pushing,
   so they take the job themselves. That is the landing, whose bar is brand + two
   dropdowns + language + CTA and carries no link row at all.

   An adjacent-sibling selector rather than `margin-left: auto` on the base rule,
   because with both the nav AND the icons claiming the free space flex would SPLIT
   it between them — the section nav would drift away from the icons on every app
   page, which is a subtle regression on four pages to fix one. Here the condition
   is exactly the situation: nothing between the brand and the icons. */
.brand + .topbar-icons { margin-left: auto; }
/* And the seam goes with it, for the reason it exists: it states the boundary
   between places you GO and controls that act on where you already are. With no nav
   there is no boundary, so the hairline is a rule dividing one group from nothing.
   The responsive block at the end of this file drops it at 820px on exactly this
   argument — the landing simply meets the condition at every width. */
.brand + .topbar-icons::before { display: none; }

/* The seam between the two groups.
   A hairline that FADES OUT at both ends rather than stopping: a hard 1px segment
   reads as a fragment of a border that lost its rectangle, which is the whole
   difference between a divider and something that looks unfinished. 22px on a
   76px bar - tall enough to separate, far too short to read as a column rule.
   `--line`, not `--ink`: it should be found when looked for, not noticed.

   NO margins, and that is arithmetic rather than laziness. The rule is a flex item
   in both directions: `.topbar`'s own --space-xl gap sits to its left (24px, the
   same distance as between two nav links, so the seam lands on the row's existing
   rhythm), and to its right --space-md (13px) plus the ~8px of empty space the 38px
   .icon-btn carries inside its circle - ~21px. Within 3px of each other, so it
   reads centred; adding a margin here double-counts a gap that already exists,
   which is how it ended up 42px on one side and 21px on the other. */
.topbar-icons::before {
  content: '';
  align-self: center;
  width: 1px; height: 22px;
  background: linear-gradient(to bottom,
              transparent,
              var(--line) 28%, var(--line) 72%,
              transparent);
}


/* --------------------------------------------------------------------------
   CHROME for PUBLIC DOCUMENT pages: brand + language only
   Used by seven pages — the three legal documents and the four content pages
   (/learn, /faq, /pricing, /contact) — hence system.css rather than a page file.

   Deliberately NOT the shared .topbar above: every link in the section nav
   leads to an authenticated area, and the account icon offers a destination a
   reader without an account cannot use. These pages are read by people who may
   have no account at all — often BEFORE signing up. So they get the brand (to
   go back) and the language picker (the page exists in two languages), nothing
   else: no hamburger and no mobile menu, since there is no nav to collapse.
   -------------------------------------------------------------------------- */

.doc-topbar {
  height: var(--topbar-height);
  padding: 0 var(--space-xl);
  display: flex; align-items: center; justify-content: space-between;
  background: var(--paper);
  border-bottom: 1px solid var(--line-soft);
}

/* The way home: a quiet link, not a button — a document should not compete with its
   own content for attention. `house` rather than a back arrow, because it names the
   DESTINATION instead of the gesture: for a signed-in reader nav.js retargets it to
   /designer (see AUTH.md), so "back" would be describing a history step it never takes.

   It carries NO geometry of its own and that is the point. It is the first child of
   each page's <main>, so its left edge is the column's left edge — i.e. the title's,
   on all seven pages, none of which share a width (learn 1100, faq/pricing 1000,
   legal 760, contact 620). It used to sit OUTSIDE <main> with `max-width: 760px;
   margin: … auto`, which cannot centre anything: auto margins do not centre an
   inline-level box, so the link hugged the viewport edge and lined up with the text
   only on the one page whose column happened to be 760. */
.doc-back {
  display: inline-flex; align-items: center; gap: .35rem;
  /* Tighter than the space above it (the column's own padding), so the link groups
     with the TITLE it sits over rather than with the bar it sits under — at
     --space-xl it read as a third piece of chrome floating between the two. */
  margin-bottom: var(--space-md);
  font-family: var(--body);
  font-size: var(--text-sm); font-weight: 600;
  color: var(--ink-soft); text-decoration: none;
  transition: color var(--dur-fast);
}
.doc-back:hover { color: var(--ink); }
.doc-back svg { width: 15px; height: 15px; }
.doc-back:focus-visible {
  outline: 2px solid var(--cocoa);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Cross-links between the four public content pages (/learn, /faq, /pricing,
   /contact), built by partials/content-links.html. Here rather than in a page file
   because all four carry the row and they load four different page stylesheets —
   the same reason .doc-topbar lives in system.css.

   A closing row, not navigation: it sits at the end of the reading and is quiet
   enough not to compete with the page's own CTA. */
.doc-links {
  display: flex; align-items: baseline;
  flex-wrap: wrap; gap: var(--space-md);
  margin-top: 3rem;
  padding-top: var(--space-lg);
  border-top: 1px solid var(--line-soft);
}
.doc-links-label {
  font-family: var(--body);
  font-size: var(--text-xs); font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-faded);
}
.doc-links a {
  font-family: var(--body);
  font-size: var(--text-sm); font-weight: 600;
  color: var(--ink-soft); text-decoration: none;
  transition: color var(--dur-fast);
}
.doc-links a:hover { color: var(--ink); text-decoration: underline; }
.doc-links a:focus-visible {
  outline: 2px solid var(--cocoa);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}


/* --------------------------------------------------------------------------
   LAYOUT: topbar icon buttons (help, language, account)
   Three identical 38px discs, in that order — and the order is not arbitrary. The
   corner is the best target on the bar (no precision needed, and it is where a hand
   goes by habit looking for "me"), so the account takes it; the middle of the
   cluster is the weakest spot, which is the right home for a set-once preference
   like the language; the help hand sits against the nav because it opens pages, like
   the links beside it. Pinned by test_the_topbar_icons_keep_their_order.
   Identical is the point too: they are
   peers, so none of them wears a standing background. The account link used to
   carry a permanent paper-2 disc ("you are here") and it read as the loudest thing
   on a quiet band — the one control that always looked pressed. Hover is the only
   fill any of them has now, plus the open state of the two dropdowns.
   -------------------------------------------------------------------------- */

.icon-btn {
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none;
  border-radius: 50%;
  color: var(--ink-soft); cursor: pointer;
  text-decoration: none;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.icon-btn:hover { background: var(--paper-2); color: var(--ink); }
.icon-btn:active { transform: scale(.95); }
.icon-btn svg { width: 21px; height: 21px; stroke-width: 1.7; }

/* The creations-left badge, on the account icon and nowhere else. Built by nav.js
   `bfAccountCount` — the ONE place a balance appears outside /account.

   `position: relative` on the LINK, not on `.icon-btn`: the badge belongs to this one
   control, and making every icon button a positioning context would invite the next
   badge onto the language globe.

   It overlaps the disc's bottom-right corner rather than sitting beside it: at 38px there
   is no room beside, and the topbar's icon row has no spare width. The paper ring is what
   keeps the overlap legible against whatever the icon is over — a badge butting straight
   against a stroke reads as part of the glyph. */
.acct-link { position: relative; }
.acct-count {
  position: absolute;
  right: -1px; bottom: -1px;
  min-width: 17px; height: 17px;
  padding: 0 4px;
  box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--cocoa-dk); color: var(--paper);
  /* Not `--text-2xs`: that step is for uppercase micro-labels in prose, and at 17px this
     needs its own value to stay centred and legible. Deliberately the one raw size in
     this file — the alternative is a scale step nothing else would ever use. */
  font-family: var(--heading);
  font-size: 10px; font-weight: 700; line-height: 1;
  /* Tabular, for the same reason as the /account hero: a badge that changes width as it
     counts down makes the icon appear to shift. */
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--paper);
  pointer-events: none;   /* the whole disc is the link; the badge must not eat the click */
}
/* Nothing left. The one state that has to be read as a condition rather than a count,
   because it is the state that stops the app working — same burgundy the quota bar uses
   at the limit, so "you are out" is one colour across the product. */
.acct-count--empty { background: var(--burgundy); }
/* `[hidden]` stated explicitly, because the UA rule that implements it is a TYPE-level
   `display: none` and the rule above is a class — the class wins, so `el.hidden = true`
   changed nothing and the badge stayed on screen carrying its last number. Caught only by
   rendering it: the state it breaks is an admin or a run with quotas off, i.e. exactly the
   accounts that do not have a balance to show. Any `display` on an element that is ever
   hidden by the attribute needs this line. */
.acct-count[hidden] { display: none; }


/* --------------------------------------------------------------------------
   LAYOUT: topbar dropdown (ONE implementation, two users)
   The language picker (`languages`, on every page) and the help menu
   (`hand-helping`, in the signed-in topbar) are the same object: an .icon-btn
   trigger with a small anchored menu under it. Named .tb-drop rather than
   .lang-dropdown since the second one landed — the alternative was a
   byte-identical copy of the menu box, its arrow and its keyframes under another
   prefix.

   Both are opened by bfDropToggle() in nav.js and closed by the shared
   bfCloseOnOutside('.tb-drop'). Nothing is per-user any more except `.sel`: only
   the language menu has a current choice. The trigger used to print the current
   language beside the globe (`.lang-trigger` / `.lang-code`, an auto-width pill);
   the icon now carries it alone, so the three topbar buttons are the same shape
   and the selected language is stated where the choice is made — in the open menu,
   by `.sel` — plus in the button's accessible name, which is where a removed
   visual cue has to go.
   -------------------------------------------------------------------------- */

.tb-drop { position: relative; }
/* Open state on whichever .icon-btn is the trigger, so a new dropdown needs no
   rule of its own. */
.tb-drop.open .icon-btn { background: var(--shortcrust); color: var(--ink); }
.tb-drop-menu {
  display: none;
  position: absolute; top: calc(100% + 10px); right: 0;
  min-width: 108px;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  padding: 5px; z-index: var(--z-menu);
  flex-direction: column; gap: 2px;
  animation: tbDropIn .14s ease-out;
}
.tb-drop-menu::before {
  content: '';
  position: absolute; top: -5px; right: 13px;
  width: 10px; height: 10px;
  background: var(--paper);
  border-left: 1px solid var(--line); border-top: 1px solid var(--line);
  transform: rotate(45deg);
  border-radius: 2px 0 0 0;
}
.tb-drop.open .tb-drop-menu { display: flex; }
@keyframes tbDropIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* An option is a <div onclick> in the language menu and an <a href> in the
   guides menu — hence text-decoration and white-space here rather than assuming
   a div. */
.tb-drop-opt {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-sm) .7rem;
  border-radius: var(--radius-xs);
  font-family: var(--body); font-size: var(--text-base); font-weight: 600;
  color: var(--ink-soft); cursor: pointer;
  text-decoration: none; white-space: nowrap;
  transition: background .12s, color .12s;
}
.tb-drop-opt:hover { background: var(--paper-2); color: var(--ink); }
.tb-drop-opt:focus-visible { outline: 2px solid var(--cocoa); outline-offset: -2px; }
.tb-drop-opt.sel { background: var(--shortcrust); color: var(--ink); }
/* Selected-option checkmark, drawn with CSS borders (no glyph, no icon font)
   so it renders identically regardless of the Lucide CDN being available. */
.tb-drop-opt.sel::after {
  content: ''; margin-left: var(--space-sm);
  width: 5px; height: 9px;
  border: solid var(--cocoa-dk); border-width: 0 2px 2px 0;
  transform: rotate(45deg); transform-origin: center;
}


/* --------------------------------------------------------------------------
   LAYOUT: mobile hamburger + menu (built by nav.js bfMobileMenu)
   -------------------------------------------------------------------------- */

.hamburger {
  display: none;
  background: none; border: none; cursor: pointer;
  width: 36px; height: 36px;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.hamburger span {
  display: block; width: 20px; height: 2px;
  background: var(--ink); border-radius: 2px;
  transition: all var(--dur-base);
}
.hamburger.open span:nth-child(1) { transform: rotate(45deg) translate(2.5px, 2.5px); }
.hamburger.open span:nth-child(2) { opacity: 0; }
.hamburger.open span:nth-child(3) { transform: rotate(-45deg) translate(2.5px, -2.5px); }

.mobile-menu {
  display: none;
  position: fixed; top: 56px; left: 0; right: 0;
  background: var(--paper); border-bottom: 1px solid var(--line-soft);
  padding: var(--space-md) var(--space-lg);
  z-index: var(--z-mobile-menu);
  flex-direction: column; gap: var(--space-xs);
  box-shadow: var(--shadow-md);
}
.mobile-menu.open { display: flex; }
.mobile-menu a {
  text-decoration: none; color: var(--ink-soft);
  padding: .6rem var(--space-md);
  border-radius: var(--radius-xs); font-size: var(--text-lg); font-weight: 600;
}
.mobile-menu a.active { background: var(--paper-2); color: var(--ink); }


/* --------------------------------------------------------------------------
   COMPONENTS: button
   One base + variants. Per the Ink CTA Rule there is a single .btn--primary
   per surface; everything else steps down to ghost / save / quiet.
   Sizes: default, .btn--sm, .btn--lg. Shapes: pill (default), .btn--square.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  padding: var(--space-sm) var(--space-lg);
  border: 1.5px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--heading);
  font-size: var(--text-lg); font-weight: 600;
  line-height: 1.2;
  color: var(--ink-soft);
  background: transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-base), border-color var(--dur-fast), color var(--dur-fast), transform var(--dur-fast), box-shadow var(--dur-base);
}
.btn:active:not(:disabled) { transform: scale(.97); }
.btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }

/* Primary: the dark ink pill. */
.btn--primary {
  background: var(--ink); color: var(--paper);
  box-shadow: var(--shadow-cta);
}
.btn--primary:hover:not(:disabled) {
  background: var(--cocoa-dk);
  transform: translateY(-1px);
  box-shadow: var(--shadow-cta-h);
}
.btn--primary:disabled { background: var(--ink-faded); box-shadow: none; }

/* Ghost: bordered secondary action. */
.btn--ghost { border-color: var(--line); color: var(--ink-soft); }
.btn--ghost:hover:not(:disabled) { border-color: var(--cocoa); color: var(--ink); background: var(--paper-2); }

/* Quiet: borderless tertiary action (sign out, discreet delete). */
.btn--quiet { color: var(--ink-soft); }
.btn--quiet:hover:not(:disabled) { color: var(--ink); background: var(--paper-2); }
.btn--danger { color: var(--burgundy); }
.btn--danger:hover:not(:disabled) { color: var(--paper); background: var(--burgundy); }

/* Save: the basil success action. */
.btn--save { background: var(--basil); color: var(--paper); }
.btn--save:hover:not(:disabled) { background: var(--basil-hover); transform: translateY(-1px); }
/* Reset: "put the parameters back", the one escape hatch in a panel of controls.
   Used on /designer, /textures (end of the sidebar scroll area) and /lettering
   (above Crea) — three positions, one control, because in all three it sits after
   the parameters it acts on and before the action.

   It is a small FRAMED TRACK, not a bare line and not a full-width slot: borderless
   it was text floating on the panel with nothing tying it to anything, and
   full-width it took the CTA's own footprint one step above the CTA. Sized to its
   content and tinted, it reads as one quiet control. The translucent fill is also
   what lets one rule work on both backgrounds it lives on — `--paper` in the
   designer sidebar and `--paper-2` inside the lettering config card — where an
   opaque colour would have had to match one of them.

   Body font at --text-sm, against the heading voice of every real action: this is
   plumbing, and it must be findable without ever being the thing you read first.
   The size is not reduced further — the padding and the fit-content width are what
   make it compact; a smaller face would just be a label nobody can read.

   **Tinted toward burgundy**, the palette's alert/undo tone, because that is what
   the control is: it throws away tuning the user did on purpose. Only a *lean* —
   6% fill, 18% border, the glyph at full strength — since it discards settings, not
   content, and `.btn--danger`'s full burgundy belongs to deletions. The tint comes
   from `--burgundy-rgb`, the documented channel token, so it follows a change to the
   hex; hover deepens the same three values instead of introducing a fourth. */
.btn--reset {
  gap: var(--space-xs);
  padding: .3rem .75rem;
  background: rgb(var(--burgundy-rgb) / .06);
  border-color: rgb(var(--burgundy-rgb) / .18);
  border-radius: var(--radius-md);
  font-family: var(--body);
  font-size: var(--text-sm); font-weight: 500;
  color: var(--ink-soft);
}
.btn--reset:hover:not(:disabled) {
  background: rgb(var(--burgundy-rgb) / .1);
  border-color: rgb(var(--burgundy-rgb) / .38);
  color: var(--burgundy);
}
/* 14px, not the `.btn svg` default of 1.05em: at --text-sm that em would render an
   11px broom, which at this stroke weight reads as a smudge rather than a tool. */
.btn--reset svg { width: 14px; height: 14px; color: var(--burgundy); }

/* Sizes */
.btn--sm { padding: .32rem .8rem; font-size: var(--text-base); gap: .3rem; }
.btn--lg { padding: .7rem 1.2rem; font-size: var(--text-xl); }
.btn--block { width: 100%; }

/* Square: the floating actions over the designer/lettering canvas. Soft 8px
   corners instead of a pill, so they read as tools rather than CTAs. */
.btn--square { border-radius: var(--radius-xs); }
.btn--float {
  background: var(--paper); color: var(--ink-soft);
  border-color: var(--line);
  box-shadow: var(--shadow-md);
}
.btn--float:hover:not(:disabled) { border-color: var(--cocoa); color: var(--ink); }
/* .btn--float + .btn--primary/.btn--save: keep the filled background, just
   inherit the floating shadow. */
.btn--primary.btn--float, .btn--save.btn--float { border-color: transparent; }
.btn--primary.btn--float { background: var(--ink); color: var(--paper); }
.btn--save.btn--float { background: var(--basil); color: var(--paper); }
/* The filled floats must also keep their light label ON HOVER: `.btn--float:hover`
   sets `color: var(--ink)` for the paper-backed variant, and being LATER in this
   file at equal specificity it used to win over `.btn--save:hover` — dark ink on
   basil green, i.e. the label went unreadable exactly while pointed at. Restated
   per variant rather than reordered, because the paper float genuinely needs the
   dark hover; only the filled ones are the exception. */
.btn--primary.btn--float:hover:not(:disabled),
.btn--save.btn--float:hover:not(:disabled) { color: var(--paper); }


/* --------------------------------------------------------------------------
   COMPONENTS: split button (primary action + a menu of alternates)
   Used by the designer's Overwrite / "Save as new" pair.
   -------------------------------------------------------------------------- */

.split-btn { position: relative; display: inline-flex; }
/* Square off the right side ONLY while the toggle is actually beside it. The
   toggle is hidden unless the result was reopened from the library
   (updateSaveButtons), so applying this unconditionally left the ordinary
   save-to-library button (`saveLibrary`) with two squared-off right corners and
   no neighbour to justify them — it read as clipped.
   Driven by a class, NOT by `:has([style*="display:none"])`: the CSSOM
   serialises `el.style.display = 'none'` as `display: none` WITH a space, so an
   inline-style substring match silently stops working the moment JS touches it
   (the static markup has no space, so it would have looked correct at first). */
.split-btn.is-split > .btn {
  border-top-right-radius: 0; border-bottom-right-radius: 0;
  padding-right: .8rem;
}
.split-btn-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px;
  border: none; cursor: pointer;
  border-left: 1px solid rgb(var(--paper-rgb) / .25);
  border-top-right-radius: var(--radius-xs); border-bottom-right-radius: var(--radius-xs);
  background: var(--basil); color: var(--paper);
  box-shadow: var(--shadow-md);
  transition: background var(--dur-base);
}
.split-btn-toggle:hover { background: var(--basil-hover); }
.split-btn-toggle svg { width: 15px; height: 15px; }
.split-btn-menu {
  display: none;
  position: absolute; bottom: calc(100% + 6px); right: 0;
  min-width: 100%;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  padding: 4px; z-index: var(--z-menu);
  white-space: nowrap;
}
.split-btn.open .split-btn-menu { display: block; }
.split-btn-menu button {
  display: block; width: 100%;
  padding: .4rem .7rem;
  border: none; background: none;
  border-radius: var(--radius-xs);
  font-family: var(--body); font-size: var(--text-base); font-weight: 600;
  color: var(--ink); text-align: left; cursor: pointer;
}
.split-btn-menu button:hover { background: var(--shortcrust); }


/* --------------------------------------------------------------------------
   COMPONENTS: card / section card
   .card = a warm paper-2 container. .sec = a titled section card with an
   optional plain-language description; used by the designer sidebar and the
   lettering config panel.
   -------------------------------------------------------------------------- */

.card {
  background: var(--paper-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  box-shadow: var(--shadow);
}
.card--lift { transition: transform var(--dur-fast), box-shadow var(--dur-fast); }
.card--lift:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }

.sec {
  background: var(--paper-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  padding: .85rem .9rem;
  margin-bottom: .7rem;
}
.sec-title {
  font-family: var(--heading);
  font-size: var(--text-xl); font-weight: 600;
  color: var(--ink);
  display: flex; align-items: center; justify-content: space-between;
}
/* Plain-language explainer under a section title: quiet copy, not a callout
   box - the card itself already separates the section. */
.sec-desc {
  font-family: var(--body);
  font-size: var(--text-sm); line-height: 1.4;
  color: var(--ink-faded);
  margin-top: .1rem;
}
.sec-title + .sec-desc { margin-bottom: .6rem; }
.sec-title:last-child, .sec-desc:last-child { margin-bottom: 0; }


/* --------------------------------------------------------------------------
   COMPONENTS: field - label above control (stacked) and row (label | control)
   -------------------------------------------------------------------------- */

.field { margin-top: .55rem; }
.field-label {
  display: block;
  font-family: var(--body); font-size: var(--text-base); font-weight: 500;
  color: var(--ink);
  margin-bottom: .22rem;
}
.field-hint {
  font-family: var(--body); font-style: italic;
  font-size: var(--text-xs); color: var(--ink-faded);
  margin-top: .18rem;
}
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-sm); }

.row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-sm); padding: var(--space-xs) 0;
}
.row-label {
  font-family: var(--body); font-size: var(--text-base);
  color: var(--ink); flex: 1;
}
.row-label small {
  display: block; font-style: italic;
  color: var(--ink-faded); font-size: var(--text-xs);
  margin-top: 1px;
}


/* --------------------------------------------------------------------------
   COMPONENTS: text input / textarea
   -------------------------------------------------------------------------- */

.input {
  width: 100%;
  padding: .5rem .7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body); font-size: var(--text-md);
}
.input:focus { outline: none; border-color: var(--cocoa); background: var(--focus-bg); }
.input::placeholder { color: var(--ink-faded); }
.input--italic { font-style: italic; }
.input--textarea { min-height: 75px; resize: vertical; line-height: 1.6; }


/* --------------------------------------------------------------------------
   COMPONENTS: field - number stepper (joined [- value +])
   -------------------------------------------------------------------------- */

.num-input {
  display: flex; align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--paper);
  overflow: hidden;
  flex-shrink: 0;
}
.num-input button {
  width: 30px; height: 32px;
  background: transparent; border: none;
  color: var(--cocoa); cursor: pointer;
  font-size: var(--text-lg); font-weight: 700;
  font-family: var(--body);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.num-input button:hover { background: var(--shortcrust); color: var(--ink); }
.num-input input {
  width: 50px; height: 32px;
  border: none; background: transparent;
  text-align: center;
  font-family: var(--body);
  font-size: var(--text-base); font-weight: 600;
  color: var(--ink);
  outline: none;
  -moz-appearance: textfield;
}
.num-input input::-webkit-outer-spin-button,
.num-input input::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
/* The value cell: number + unit, centered as one group between the buttons. */
.num-val {
  display: inline-flex; align-items: center;
  justify-content: center;
  flex: 1;
  min-width: 56px;
  height: 32px;
}
.num-val input {
  width: 34px; height: 32px;
  border: none; background: transparent;
  text-align: right;
  font-family: var(--body);
  font-size: var(--text-base); font-weight: 600;
  color: var(--ink);
  outline: none;
  padding: 0;
  -moz-appearance: textfield;
  line-height: 32px;
}
.num-val .unit {
  font-size: var(--text-sm); color: var(--ink-soft);
  pointer-events: none;
  padding-left: 3px;
  line-height: 32px;
}
/* Full-width stepper inside a stacked .field (the config cards). */
.num-input--block { width: 100%; }


/* --------------------------------------------------------------------------
   COMPONENTS: field - custom select dropdown (.csel)
   -------------------------------------------------------------------------- */

.csel {
  position: relative;
  flex-shrink: 0;
}
.csel-trigger {
  height: 32px; padding: 0 1.6rem 0 .7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  font-family: var(--body); font-size: var(--text-base); font-weight: 500;
  color: var(--ink); cursor: pointer;
  display: inline-grid; align-items: center;
  grid-template-columns: 1fr auto;
  transition: border-color var(--dur-fast), background-color var(--dur-fast);
  white-space: nowrap;
}
.csel-text { grid-column: 1; grid-row: 1; }
.csel-trigger:hover { border-color: var(--cocoa); background-color: var(--focus-bg); }
.csel.open .csel-trigger { border-color: var(--cocoa); background-color: var(--focus-bg); }
.csel-arrow {
  position: absolute; right: .6rem; top: 50%;
  width: 8px; height: 8px;
  border-right: 1.5px solid var(--cocoa); border-bottom: 1.5px solid var(--cocoa);
  transform: translateY(-70%) rotate(45deg);
  transition: transform var(--dur-base);
  pointer-events: none;
}
.csel.open .csel-arrow { transform: translateY(-30%) rotate(225deg); }
.csel-menu {
  position: absolute; top: calc(100% + 4px); left: 0;
  min-width: 100%; max-height: 200px; overflow-y: auto;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 4px;
  z-index: var(--z-menu);
  display: none;
}
.csel.open .csel-menu { display: block; }
.csel-opt {
  padding: .4rem .7rem;
  border-radius: var(--radius-xs);
  font-family: var(--body); font-size: var(--text-sm);
  color: var(--ink); cursor: pointer;
  transition: background .12s;
  white-space: nowrap;
}
.csel-opt:hover { background: var(--shortcrust); }
/* Full-width select inside a stacked .field. */
.csel--block, .csel--block .csel-trigger { width: 100%; }


/* --------------------------------------------------------------------------
   COMPONENTS: chip (filter / example / suggestion pill)
   -------------------------------------------------------------------------- */

.chips { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
/* `text-decoration: none` because a chip is a <span>, a <button> AND an <a>: the
   designer's example prompts and the shop's filters are spans, the crop tools are
   buttons, and /contact's prefilled-email topics are real `mailto:` links (so they
   work without JS and can be right-clicked). Same reason `.tb-drop-opt` declares it
   — the component cannot assume which element it is on, and the anchor is the one
   that arrives underlined. */
.chip {
  display: inline-flex; align-items: center; gap: .25rem;
  font-family: var(--body);
  font-size: var(--text-base); font-weight: 500;
  padding: .22rem .7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
  user-select: none;
  text-decoration: none;
  transition: all var(--dur-fast);
}
.chip:hover { border-color: var(--cocoa); color: var(--ink); background: var(--focus-bg); }
/* Selected filter chip: dark ink, matching the category rails. */
.chip.active {
  background: var(--ink); border-color: var(--ink); color: var(--paper);
  font-weight: 600;
}


/* --------------------------------------------------------------------------
   COMPONENTS: tile (mode selector - icon over label, selected = shortcrust)
   -------------------------------------------------------------------------- */

.tiles { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-sm); }
.tile {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: .5rem .65rem;
  background: var(--paper);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  font-family: var(--body); font-size: var(--text-base); font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
  transition: all var(--dur-fast);
}
.tile:hover { border-color: var(--shortcrust-dk); background: var(--focus-bg); }
.tile.active {
  background: var(--shortcrust); border-color: var(--crust);
  font-weight: 600;
  box-shadow: var(--shadow);
}
.tile img { width: 26px; height: 26px; object-fit: contain; flex-shrink: 0; }
.tile svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--cocoa); }


/* --------------------------------------------------------------------------
   COMPONENTS: badge (plan tier, build mode, status)
   Non-interactive by definition - a badge reports state, never acts.
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .12rem .55rem;
  border-radius: var(--radius-pill);
  font-family: var(--body);
  font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  background: var(--shortcrust); color: var(--cocoa-dk);
  cursor: default;
}
/* --current = "you have this" (the /account plan card, and the plan badge on a
   lettering font your plan includes); --locked = "an upgrade buys this". On the
   font cards EVERY card is badged with the plan it belongs to, so the two
   variants sit side by side down one list and must stay easy to tell apart. */
.badge--current { background: rgb(var(--basil-rgb) / .16); color: var(--basil); }
.badge--locked { background: var(--paper-3); color: var(--ink-faded); }


/* --------------------------------------------------------------------------
   COMPONENTS: segmented control (2D/3D, Describe/Upload)
   An inset track with a raised paper pill on the active segment.
   -------------------------------------------------------------------------- */

.segmented {
  display: inline-flex;
  background: var(--surface-inset);
  border-radius: var(--radius-pill);
  padding: 3px;
}
.segmented button {
  padding: .35rem .95rem;
  border: none; background: transparent;
  border-radius: var(--radius-pill);
  font-family: var(--body); font-size: var(--text-base); font-weight: 600;
  color: var(--ink-soft); cursor: pointer;
  transition: all var(--dur-fast);
}
.segmented button:hover:not(.active) { color: var(--ink); }
.segmented button.active { background: var(--paper); color: var(--ink); box-shadow: var(--shadow); }
/* There was a `--sq` (squared) variant here for the auth card's Log in / Sign up
   tabs. It went with them: a magic link is one address and one button, so there is
   no longer a choice to segment, and a modifier with no call site is a component
   nobody can judge. */


/* --------------------------------------------------------------------------
   COMPONENTS: quota (the balance, on /account and nowhere else)
   ONE consumer, and that is the point of the shape below rather than an accident
   waiting to be generalised.

   It used to be shared three ways — a `--compact` box in the designer sidebar, a
   `-note` line under /lettering's CTA, and /account's `--hero`. Three surfaces
   printing one number in three formats, each of which had to know whether that
   number resets monthly, i.e. three places a promise about money could drift. The
   two smaller ones were replaced by `.acct-count`, the badge on the topbar account
   icon: shared chrome cannot disagree with itself, and it sits on the control that
   opens the page where the number is explained in full.

   So the variants are GONE and the hero IS the component. Do not re-add a modifier
   to put this back in a sidebar; add a caller for `bfAccountCount` instead.

   No box: inside the profile card a bordered panel would be a card within a card,
   and a `--text-5xl` numeral needs no container to be found — its size is the
   container. And the value is the REMAINING count, not `used / limit`: /account is
   opened to decide whether to buy, and "17" answers that where "8 / 25" asks the
   reader to subtract first. The totals stay one line down, on the bar they describe.
   -------------------------------------------------------------------------- */

.quota {
  text-align: right;
  /* The meter's own width, so the bar is the same object under every label — sized
     against the longest string it carries, which is `accUsageRemaining` in Italian.
     Declared HERE rather than as a fixed grid track in account.css so the track can
     still collapse to nothing when there is no balance to show. */
  min-width: 250px;
}
.quota-value {
  display: block;
  font-family: var(--heading);
  font-size: var(--text-5xl); font-weight: 700;
  color: var(--ink);
  line-height: 1;
  /* Tabular figures so the balance does not change WIDTH as it counts down: with
     proportional numerals a right-aligned "8" sits at a different optical position
     from "18", and the label under it would appear to shift on every generation. */
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.quota-label {
  display: block;
  margin-top: .3rem;
  font-family: var(--body);
  font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-soft);
}
/* The meter's two ends: what the bar measures, and what it measures against. A row
   rather than a caption, because the pair is one sentence read left to right — the
   label names the bar and the total closes it. Faded, so the context does not compete
   with the balance above it. */
.quota-meter {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-md);
  margin-top: 1.15rem; margin-bottom: .4rem;
  font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faded);
}
.quota-bar {
  height: 5px;
  border-radius: var(--radius-pill);
  background: var(--line-soft);
  overflow: hidden;
}
.quota-bar-fill {
  height: 100%;
  background: var(--cocoa);
  border-radius: var(--radius-pill);
  transition: width .25s var(--ease);
}
/* Exhausted. Same burgundy as `.acct-count--empty`, so "you are out" is one colour
   wherever the app says it. */
.quota-bar-fill.full { background: var(--burgundy); }


/* --------------------------------------------------------------------------
   COMPONENTS: overlay shell (the ONE dimmed-backdrop container)
   Built by bfOverlay() in nav.js. Two consumers: the confirmation dialog
   (bfConfirm, account.css keeps its inner parts) and the top-up catalogue the
   designer and /lettering open when a balance runs out.

   It was `.bf-confirm-overlay` in account.css until the second consumer arrived —
   the same move, for the same reason, as .plan-card below: two dimmed backdrops
   styled to look alike are two things to keep looking alike, and the designer
   does not load account.css at all, so a copy there was the only alternative.

   `--wide` exists because the two contents differ by a lot: one sentence and two
   buttons versus three price cards. Everything else — the dim, the centring, the
   paper box — is shared, which is the part that has to match.
   -------------------------------------------------------------------------- */

.bf-overlay {
  position: fixed; inset: 0; z-index: var(--z-modal);
  background: rgb(var(--ink-rgb) / .45);
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-xl);
  /* The catalogue is taller than a short viewport once the cards stack (the
     responsive block turns .plan-cards into one column), so the BACKDROP scrolls
     rather than the box being clipped with its buy buttons out of reach. */
  overflow-y: auto;
}
.bf-overlay-box {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  max-width: 380px; width: 100%;
  padding: 1.6rem var(--space-xl);
  /* Own margin instead of relying on the flex centring alone: with `auto` margins
     a box taller than the viewport would have its top cut off unreachably. */
  margin: auto;
}
.bf-overlay-box--wide { max-width: 900px; }

/* The top-up catalogue's own three parts. Everything that draws a PACK is the
   .plan-card family above — these are only the frame around it, which is why there
   is no card styling here at all. */
.bf-topup-title {
  margin: 0 0 var(--space-xs);
  font-size: var(--text-2xl); color: var(--ink);
}
.bf-topup-lead {
  margin: 0 0 var(--space-lg);
  font-size: var(--text-base); color: var(--ink-soft); line-height: 1.5;
}
.bf-topup-foot {
  display: flex; justify-content: center;
  margin-top: var(--space-lg);
}


/* --------------------------------------------------------------------------
   COMPONENTS: plan card
   ONE price card, used by TWO screens: /account (built by account.js — the
   management view, with the current-plan badge and the upgrade/downgrade
   footer) and /pricing (server-rendered — the public view, with a tagline and a
   single link). It was account.css's until /pricing landed; two screens showing
   the same three plans in two different-looking cards is the drift this move
   prevents, and it is the same reasoning that brought .doc-topbar here when
   /faq became a second consumer.

   What each page keeps for itself: .plan-selector / .plan-pending-note
   (account.css) and the tagline/note around the row (pricing.css).
   -------------------------------------------------------------------------- */

.plan-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-lg);
  align-items: stretch;
}
/* Composes the shared .card, inverting to the plain paper surface: on /account it
   has to stand out from the paper-2 cards around it (profile, library), and on
   /pricing the border + shadow are what lift it off the page. */
.plan-card {
  position: relative;
  background: var(--paper);
  padding: var(--space-xl) 1.2rem 1.2rem;
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
}
/* Bordered in crust rather than scaled up: scaling a card in a 3-up grid clips
   its ribbon and misaligns the footers. Two different reasons for the same
   emphasis — `.current` is the plan you are ON (/account), `--best` the one we
   recommend (/pricing) — so they share the rule, not the meaning. */
.plan-card.current,
.plan-card--best { border: 2px solid var(--crust); }
.plan-card-ribbon {
  position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
  padding: .15rem .7rem;
  background: var(--crust); color: var(--paper);
  border-radius: var(--radius-pill);
  font-family: var(--body);
  font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  white-space: nowrap;
}
.plan-card-name {
  font-family: var(--heading);
  font-size: var(--text-xl); font-weight: 600;
  color: var(--ink);
  margin-bottom: var(--space-sm);
}
.plan-card-price { margin-bottom: var(--space-lg); }
.plan-card-amount {
  display: block;
  font-family: var(--heading);
  font-size: var(--text-3xl); font-weight: 700;
  color: var(--ink);
  line-height: 1.1;
}
.plan-card-period {
  font-family: var(--body);
  font-size: var(--text-xs); color: var(--ink-faded);
}
.plan-card-features {
  list-style: none;
  width: 100%;
  display: flex; flex-direction: column; gap: .55rem;
  margin-bottom: var(--space-xl);
  text-align: left;
}
.plan-card-features li {
  display: flex; align-items: flex-start; gap: var(--space-sm);
  font-family: var(--body);
  font-size: var(--text-base); color: var(--ink-soft);
}
.plan-card-features svg {
  width: 15px; height: 15px;
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--basil);
}
/* Footer sits at the bottom of every card so the actions line up across the
   row regardless of how many feature bullets each plan has. */
.plan-card-footer {
  margin-top: auto;
  width: 100%;
  display: flex; flex-direction: column; align-items: stretch; gap: var(--space-sm);
}
.plan-card-footer .badge { justify-content: center; padding: .35rem 1rem; }




/* --------------------------------------------------------------------------
   COMPONENTS: floating stage chrome (preview card, view tabs, round controls)
   Shared by the designer stage and the lettering preview stage.
   -------------------------------------------------------------------------- */

.stage-preview-card {
  position: absolute; top: var(--space-lg); left: var(--space-lg);
  z-index: var(--z-stage);
  background: var(--paper);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  padding: .7rem .9rem;
  box-shadow: var(--shadow-md);
  max-width: 300px;
}
.stage-preview-title {
  font-family: var(--heading);
  font-size: var(--text-lg); font-weight: 600;
  color: var(--ink);
  margin-bottom: var(--space-xs);
}
.stage-preview-meta { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.stage-preview-meta span {
  font-family: var(--body);
  font-size: var(--text-xs);
  background: var(--paper-2);
  border: 1px solid var(--line-soft);
  color: var(--ink-soft);
  padding: .15rem .55rem;
  border-radius: var(--radius-sm);
}

.view-tabs {
  position: absolute; top: var(--space-lg); right: var(--space-lg);
  z-index: var(--z-stage);
}

.stage-round-controls {
  position: absolute; top: 4.6rem; right: var(--space-lg);
  z-index: var(--z-stage);
  display: flex; flex-direction: column; gap: var(--space-sm);
}
.stage-round-btn {
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--paper); border: 1px solid var(--line-soft);
  border-radius: 50%;
  color: var(--ink-soft); cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: all var(--dur-fast);
}
.stage-round-btn:hover { border-color: var(--cocoa); color: var(--ink); background: var(--focus-bg); }
.stage-round-btn svg { width: 17px; height: 17px; }

/* Floating action bar over the canvas: primary group, hairline, secondary.
   No wrapping - a second row would sit over the artifact; below 1100px the row goes
   icon-only instead (see the media query under this block). */
.stage-actions {
  position: absolute; bottom: 1.4rem; left: 50%; transform: translateX(-50%);
  display: flex; gap: .55rem; align-items: center; justify-content: center;
  flex-wrap: nowrap; white-space: nowrap;
  max-width: calc(100% - 2rem);
  z-index: 5;
}
.stage-actions .btn { font-size: var(--text-md); padding: .45rem .8rem; }
/* Never let a flex item shrink below its label: `max-width` + `nowrap` above
   means the row compresses its children instead of overflowing, which clipped
   the last button's right edge (the save-to-library split button, widest of the
   row and made of two elements). Labels are dropped by media query at 1100px,
   so shrinking is never the intended way to fit. */
.stage-actions > *, .stage-actions .btn { flex-shrink: 0; }
.stage-actions-sep {
  width: 1px; height: 28px;
  background: var(--line);
  margin: 0 var(--space-xs);
}
/* Below ~1100px the labelled action row no longer fits beside the sidebar, so the
   floating buttons go icon-only (each keeps a title/aria-label). With the component,
   not in a page file: /lettering's bar carries the same save control as the designer's
   and would otherwise keep its labels at every width — which is what "the page file
   drops the labels" cost the moment a second page used this bar. */
@media (max-width: 1100px) {
  .stage-actions .btn > span { display: none; }
  .stage-actions .btn { padding: var(--space-sm); }
}


/* --------------------------------------------------------------------------
   COMPONENTS: the stage surface (designer canvas + lettering preview)
   A warm paper field with a subtle ruled texture - the product's signature
   background. Pages apply it to their own container (.stage / .fstage).
   -------------------------------------------------------------------------- */

.stage-surface {
  background:
    repeating-linear-gradient(90deg, rgba(150,120,80,.05) 0 1px, transparent 1px 24px),
    radial-gradient(circle at center, #FCFBF6 0%, #F1EADC 100%);
}


/* --------------------------------------------------------------------------
   COMPONENTS: cookie loader animation (designer + lettering)
   The rolling-cookie illustration is the brand's signature loading state. The
   motion is defined once here; only the px sizes differ per use, so each site
   sets `--cookie-size` / `--cookie-shadow-w` / `--cookie-shadow-h` instead of
   restating the block.
   -------------------------------------------------------------------------- */

@keyframes cookieRoll { 100% { transform: rotate(360deg); } }
@keyframes cookieBounce { 50% { transform: translate3d(0,-8%,0); } }
@keyframes cookieShadow { 50% { opacity: .12; transform: scale(.9); } }

/* Animated loader art (an animated webp with its own alpha), which replaces the
   SVG above when one is configured for the page — cookie on /designer and
   /lettering, rolling pin on /textures, resolved via `bfLoaderArt`.
   Lives here, NOT in designer.css: /lettering does not load that file, and its
   loader is `.cookie-load` while the designer's is `.cookie-loader` — hence both
   container names below. Hidden until the image actually LOADS (initLoaderArt
   adds `has-art` on the load event), so a missing or broken file leaves the SVG
   loader spinning instead of an empty box. When the art IS shown the CSS
   roll/bounce/shadow must step aside: the animation is already in the file, and
   running both would double the motion and stack two shadows. */
.loader-art { display: none; width: 150px; height: 150px; object-fit: contain; }
/* The live-rebuild pill is a small overlay over a finished result, not the
   full-canvas loading state, so its art matches the 56px cookie it replaces. */
.loader-art--pill { width: 72px; height: 72px; }
.cookie-loader.has-art .loader-art,
.cookie-load.has-art .loader-art,
.live-pill.has-art .loader-art { display: block; }
.cookie-loader.has-art .cookie-wrapper, .cookie-loader.has-art .cookie-shadow,
.cookie-load.has-art .cookie-wrapper, .cookie-load.has-art .cookie-shadow,
.live-pill.has-art .cookie-wrapper, .live-pill.has-art .cookie-shadow { display: none; }

/* The RE-CALCULATION pill: a small overlay over a finished result, animated with
   `mix.webp` — a different act from baking a new shape,
   which is what the full-canvas loader above says. Both editors show it (the designer's
   #live-pill, /lettering's #let-live-pill), so it lives here rather than in designer.css:
   /lettering does not load that file, and a second copy of these rules is how one page
   ends up with a pill that is invisible or off-centre. Its container must be positioned
   (#state-result, .fstage). */
.live-pill {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center;
  opacity: 0;
  transition: opacity var(--dur-base);
  z-index: 5;
  pointer-events: none;
}
.live-pill.active { opacity: 1; }
/* The pill reuses the same loader, sized down. */
.live-pill { --cookie-size: 56px; --cookie-shadow-w: 36px; --cookie-shadow-h: 5px; }
/* `.live-text` was the pill's caption and is gone with it (2026-09-04): at this size the
   words were not legible, and the animation is the message. */

.cookie-wrapper {
  backface-visibility: hidden;
  perspective: 1000px;
  transform: translate3d(0,0,0);
  animation: cookieBounce 1s infinite forwards;
}
.cookie-spin {
  width: var(--cookie-size, 90px); height: var(--cookie-size, 90px);
  outline: .5rem solid transparent;
  transform-origin: 50% 50%;
  animation: cookieRoll 2s cubic-bezier(1,0,.4,1) infinite;
}
.cookie-shadow {
  width: var(--cookie-shadow-w, 56px); height: var(--cookie-shadow-h, 6px);
  margin: 8px auto 0;
  background: var(--ink);
  border-radius: 100%;
  opacity: .2;
  animation: cookieShadow 1s infinite;
}


/* --------------------------------------------------------------------------
   COMPONENTS: thin scrollbar (opt-in utility)
   The app's scrolling panels (designer sidebar, lettering font browser) share
   one quiet scrollbar instead of the platform default. WebKit-only by design:
   Firefox gets its own thin native bar, and there is no cross-browser way to
   style this that doesn't fight one of the two.
   Width is a variable so a nested list can go narrower than its panel without
   restating the block (.flist inside .fside).
   -------------------------------------------------------------------------- */

.scroll-thin::-webkit-scrollbar { width: var(--scrollbar-w, 8px); }
.scroll-thin::-webkit-scrollbar-track { background: transparent; }
.scroll-thin::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: calc(var(--scrollbar-w, 8px) / 2);
}
.scroll-thin::-webkit-scrollbar-thumb:hover { background: var(--shortcrust-dk); }


/* --------------------------------------------------------------------------
   COMPONENTS: toast (transient message, e.g. a locked font)
   -------------------------------------------------------------------------- */

.toast {
  position: fixed; bottom: var(--space-xl); left: 50%; transform: translateX(-50%);
  background: var(--ink); color: var(--paper);
  padding: var(--space-sm) 1.2rem;
  border-radius: var(--radius-pill);
  font-family: var(--body); font-size: var(--text-base); font-weight: 600;
  z-index: var(--z-toast);
  opacity: 0; transition: opacity var(--dur-slow);
  pointer-events: none;
  box-shadow: var(--shadow-md);
}
.toast.show { opacity: 1; }


/* --------------------------------------------------------------------------
   COMPONENTS: switch (an on/off choice that applies itself)
   A real <input type="checkbox"> under a drawn track, so the keyboard, the label
   click and assistive tech all work without a line of JS. The input is not
   `display: none` — that would take it out of the tab order; it is positioned
   under the track at zero opacity, which keeps it focusable, and
   `:focus-visible + .switch-track` is what draws the ring.

   Its only consumer today is the cookie panel's analytics category. It lives here
   rather than there because it is component-shaped and the next real on/off
   setting will want it — the same argument as `.split-btn` and `.btn--danger`.
   -------------------------------------------------------------------------- */

.switch { position: relative; display: inline-flex; flex-shrink: 0; cursor: pointer; }
.switch input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.switch-track {
  width: 42px; height: 24px;
  border-radius: var(--radius-pill);
  background: var(--line);
  transition: background var(--dur-base);
  pointer-events: none;
}
/* The knob. `left` animates rather than `transform` so the track can stay the
   positioning context for it without a second transform to compose with. */
.switch-track::after {
  content: '';
  position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--paper);
  box-shadow: var(--shadow);
  transition: left var(--dur-base);
}
.switch input:checked + .switch-track { background: var(--cocoa); }
.switch input:checked + .switch-track::after { left: 21px; }
.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--cocoa);
  outline-offset: 2px;
}


/* --------------------------------------------------------------------------
   COMPONENTS: cookie consent (consent.js, every page)
   Built by JS, not written into the 14 page files: it needs no server data and a
   crawler has no use for it, so there is nothing to server-render.

   Bottom RIGHT and narrow, never a full-width bar across the foot of the page:
   the reader must be able to read the page while deciding, which is also what
   makes it fair to show once and then get out of the way. After a choice the
   panel is replaced by `.ck-disc`, a small round control that reopens it — the
   consent has to stay withdrawable, and a link buried in a policy is not that.
   -------------------------------------------------------------------------- */

.ck-panel {
  position: fixed;
  right: var(--space-lg); bottom: var(--space-lg);
  z-index: var(--z-consent);
  width: min(380px, calc(100vw - 2 * var(--space-lg)));   /* the auth card's cap */
  max-height: calc(100vh - 2 * var(--space-lg));
  overflow-y: auto;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--space-lg);
}

.ck-head {
  display: flex; align-items: center; gap: var(--space-sm);
  margin-bottom: var(--space-md);
}
.ck-head svg { width: 18px; height: 18px; color: var(--cocoa); flex-shrink: 0; }
.ck-title {
  font-family: var(--heading);
  font-size: var(--text-xl); font-weight: 600;
  color: var(--ink);
  margin-right: auto;
}
.ck-intro {
  font-family: var(--body);
  font-size: var(--text-sm); line-height: 1.55;
  color: var(--ink-soft);
  margin-bottom: var(--space-md);
}

/* One category. The inset well (the `.adv-group` figure/ground inversion) rather
   than a paper-2 card, so three of them in a column read as a list of settings
   instead of three cards competing with the panel that holds them. */
.ck-cat {
  background: var(--surface-inset);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  margin-bottom: var(--space-sm);
}
.ck-cat-head {
  display: flex; align-items: center; gap: var(--space-sm);
  margin-bottom: var(--space-xs);
}
.ck-cat-name {
  font-family: var(--body);
  font-size: var(--text-md); font-weight: 600;
  color: var(--ink);
}
/* Pushes whatever ends the row — the badge or the switch — to the right edge. */
.ck-cat-head .badge, .ck-cat-head .switch { margin-left: auto; }
.ck-cat-body {
  font-family: var(--body);
  font-size: var(--text-xs); line-height: 1.5;
  color: var(--ink-soft);
}

/* Accept and Reject side by side and the same size: an "accept" that is easier to
   press than its "reject" is the thing regulators actually fine. `Save my choices`
   is quieter because it is the third path, not a lesser one. */
.ck-actions {
  display: flex; align-items: center; gap: var(--space-sm);
  flex-wrap: wrap;
  margin-top: var(--space-md);
}
.ck-actions .btn { flex: 1 1 auto; }
.ck-actions .btn--quiet { flex-basis: 100%; }
.ck-more {
  display: inline-block;
  margin-top: var(--space-sm);
  font-family: var(--body);
  font-size: var(--text-xs); font-weight: 600;
  color: var(--cocoa-dk);
}
.ck-more:hover { color: var(--burgundy); }

/* The collapsed state: composes `.stage-round-btn` (paper disc, hairline border,
   floating shadow) and adds only where it sits. */
.ck-disc {
  position: fixed;
  right: var(--space-lg); bottom: var(--space-lg);
  z-index: var(--z-consent);
}

@media (max-width: 480px) {
  .ck-panel {
    right: var(--space-sm); bottom: var(--space-sm);
    width: calc(100vw - 2 * var(--space-sm));
    padding: var(--space-md);
  }
  .ck-disc { right: var(--space-md); bottom: var(--space-md); }
}


/* --------------------------------------------------------------------------
   REDUCED MOTION (prefers-reduced-motion: reduce)
   Honours the OS-level setting: vestibular disorders, migraine triggers, or
   simply a user who does not want things moving. Placed last in system.css so
   it wins over the component rules above; page files declare no animation, and
   their transitions all read --dur-*, which is zeroed here.

   Motion is removed, feedback is NOT: every state that was communicated by
   movement stays communicated. Three deliberate exceptions below.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  /* Kills the 26 transitions that read the duration tokens. The 5 that inline a
     literal duration are caught by the catch-all after this. */
  :root {
    --dur-fast: 0s;
    --dur-base: 0s;
    --dur-slow: 0s;
  }

  /* Catch-all for anything that hardcoded a duration or lands here later. */
  *, *::before, *::after {
    transition-duration: 0s !important;
    animation-duration: 0s !important;
    animation-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  /* The hover lift and the :active press are movement for its own sake. */
  .btn:active:not(:disabled),
  .btn--primary:hover:not(:disabled),
  .btn--save:hover:not(:disabled),
  .icon-btn:active,
  .card--lift:hover,
  .sidebar-toggle:active {
    transform: none;
  }

  /* EXCEPTION 1 — the cookie loader. It IS the "working…" indicator, and a
     frozen biscuit reads as a crash, not as calm. The rolling/bouncing motion
     is dropped (that is the part that can trigger symptoms) and replaced by a
     slow opacity pulse: still clearly alive, no movement, well under the
     3-flashes-per-second threshold. The full-canvas loader also carries a text
     status next to it (.loading-title + .loading-step), so its meaning is never
     animation-only; the lettering overlay and the rebuild pill are captionless
     by design (the button and the result behind them say what is happening). */
  @keyframes cookiePulse { 50% { opacity: .45; } }
  .cookie-wrapper {
    animation: cookiePulse 2s ease-in-out infinite !important;
    animation-duration: 2s !important;
  }
  .cookie-spin { animation: none !important; }
  .cookie-shadow { animation: none !important; opacity: .12; }
  /* The animated webp cannot be slowed or stopped by CSS — `animation: none` has
     no effect on a moving IMAGE — so under reduced motion it is hidden outright
     and the SVG loader (with its pulse above) is restored. Without this the art
     kept playing at full speed, defeating the whole block. */
  .loader-art { display: none !important; }
  .cookie-loader.has-art .cookie-wrapper, .cookie-load.has-art .cookie-wrapper,
  .live-pill.has-art .cookie-wrapper,
  .cookie-loader.has-art .cookie-shadow, .cookie-load.has-art .cookie-shadow,
  .live-pill.has-art .cookie-shadow { display: block !important; }

  /* EXCEPTION 2 — the sidebar drawer (mobile). Without its transform the panel
     would be stuck off-screen: the transform IS the open/closed state, not
     decoration. Keep the positioning, drop the travel time (handled above). */

  /* EXCEPTION 3 — the language menu's slide-in becomes an instant appearance
     rather than nothing: `animation-duration: 0s` on langMenuIn already lands
     it at its final position, which is the intended result. */
}


/* --------------------------------------------------------------------------
   FOCUS-VISIBLE (keyboard accessibility)
   Page files add their own selectors with the same outline.
   -------------------------------------------------------------------------- */

.btn:focus-visible,
.icon-btn:focus-visible,
.chip:focus-visible,
.tile:focus-visible,
.input:focus-visible,
.segmented button:focus-visible,
.view-tab:focus-visible,
.split-btn-toggle:focus-visible,
.stage-round-btn:focus-visible,
.csel-trigger:focus-visible,
.num-input button:focus-visible {
  outline: 2px solid var(--cocoa);
  outline-offset: 2px;
}

/* Text fields are the exception, and deliberately so: they ALREADY carry their
   own focus signal (border -> --cocoa plus the --focus-bg fill, from .input:focus
   above), so the offset 2px ring stacked a second contour outside the first with
   a gap between them - three signals for one state, which read heavy. A 1px
   ring flush against the border (offset 0) keeps ONE crisp contour.
   Every other control above keeps the 2px/2px ring because the ring is its only
   focus indicator - thinning it there would weaken keyboard navigation
   (WCAG 2.4.7). Contrast is unchanged: --cocoa is 3.9-4.5:1 against paper,
   paper-2 and focus-bg, past the 3:1 floor of WCAG 1.4.11. */
.input:focus-visible {
  outline: 1px solid var(--cocoa);
  outline-offset: 0;
}


/* --------------------------------------------------------------------------
   RESPONSIVE: shell only (820px main breakpoint, 480px small phones)
   -------------------------------------------------------------------------- */

@media (max-width: 820px) {
  /* The bar shrinks by moving the TOKEN, not by overwriting `height` on each bar
     with the same literal. Both bars already read `var(--topbar-height)`, so one
     line does what two did — and, more importantly, it keeps the token equal to
     the bar's real height at every width. Anything that insets itself by the
     token (a fixed bar's sticky content, legal.css's `scroll-margin-top`) was
     silently reading 76px here while the bar was 56px. */
  :root { --topbar-height: 56px; }
  .topbar { padding: 0 var(--space-md); gap: .6rem; }
  /* The public-document chrome follows the same steps, so the brand lines up
     with every other page. Here rather than in a page file: /faq uses .doc-topbar
     without loading legal.css. */
  .doc-topbar { padding: 0 var(--space-md); }
  /* .doc-back needs no responsive rule at either step: it holds no padding and no
     top margin, and the column it now sits in brings its own. */
  .brand img { height: 40px; }
  .sectnav { display: none; }
  .hamburger { display: flex; }
  .topbar-icons { margin-left: auto; gap: var(--space-xs); }
  /* No nav to separate from: the seam would sit between the brand and the icons,
     dividing nothing. */
  .topbar-icons::before { display: none; }
  /* Plan cards stack. Here rather than in a page file, with the component: both
     /account and /pricing use it and only one of them loads account.css. */
  .plan-cards { grid-template-columns: 1fr; }
  .plan-card { padding: var(--space-lg) 1.2rem 1.2rem; }
  /* Stacked cards: the ribbon would collide with the card above it. */
  .plan-card-ribbon { position: static; transform: none; margin-bottom: var(--space-sm); }
  .tiles { grid-template-columns: 1fr 1fr; }
  /* Zoom/home move to a horizontal row above the action bar. `bottom` clears
     .stage-actions (4.4rem + ~2.4rem tall): the actions span the full width when
     centred, so "beside them" is not available on a phone. */
  .stage-round-controls { top: auto; bottom: 7.2rem; right: var(--space-md); flex-direction: row; }
}

@media (max-width: 480px) {
  :root { --topbar-height: 50px; }
  .topbar { padding: 0 .6rem; }
  .doc-topbar { padding: 0 .6rem; }
  .brand img { height: 34px; }
  .stage-preview-card { max-width: calc(100% - 2rem); padding: .5rem .7rem; }
}
