@gemboss/website-ui 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/website.mjs CHANGED
@@ -6,7 +6,7 @@
6
6
  // the export line and the // comments, plus auto-init): edit this file, then run the build. Keep comments on lines of
7
7
  // their own, so the build can leave them out.
8
8
 
9
- var version = '0.11.0';
9
+ var version = '0.12.0';
10
10
  var api = { version: version, init: init, destroy: destroy };
11
11
  // Page state. Set up by the first init(), never at import: a server can import this file.
12
12
  var doc, root, mine, fine, sys, wide, desk, mid, live = [], queued = false;
@@ -554,19 +554,46 @@ function theme(ctx) {
554
554
  relabel();
555
555
  }
556
556
 
557
- // Reveal at 15% visible, once. Failsafe: shown 2.5s after the init that found them, or at once without
558
- // IntersectionObserver or under reduced motion.
557
+ // Reveal (0.12.0, as gemboss.ai; system.json motion.reveal): once, when 12% of it is in view with the bottom 6% of the
558
+ // view taken off. Each .gw-reveal after the first among its .gw-reveal siblings waits 70ms more (at most 380ms), so a
559
+ // grid cascades. A [data-gw-count] number inside counts up from 0 when its reveal shows. Failsafe: all shown 1.6s after
560
+ // the init that found them, or at once without IntersectionObserver or under reduced motion (and then nothing counts).
561
+ var REVEAL = { threshold: 0.12, margin: '0px 0px -6% 0px', stagger: 70, staggerMax: 380, failsafe: 1600, count: 1100 };
562
+ // the number in the element's first words: "$1,200+" counts from 0 to 1,200 with its sign, grouping and decimals kept
563
+ function countUp(el) {
564
+ var n = el.firstChild, m = n && n.nodeType === 3 && /^(\s*\D*?)(\d[\d,]*(?:\.\d+)?)(.*)$/.exec(n.nodeValue), t0;
565
+ if (!m) return;
566
+ var num = m[2].replace(/,/g, ''), dec = (num.split('.')[1] || '').length;
567
+ function fmt(v) { v = v.toFixed(dec); return m[1] + (num !== m[2] ? Number(v).toLocaleString('en-US') : v) + m[3]; }
568
+ n.nodeValue = fmt(0);
569
+ requestAnimationFrame(function step(ts) {
570
+ var p = Math.min((ts - (t0 = t0 || ts)) / REVEAL.count, 1);
571
+ n.nodeValue = fmt(p < 1 ? num * (1 - Math.pow(1 - p, 3)) : +num);
572
+ if (p < 1) requestAnimationFrame(step);
573
+ });
574
+ }
559
575
  function reveal(ctx) {
560
- var els = [], io = null;
561
- each(ctx, '.gw-reveal', 'reveal', function (el) { els.push(el); return { stop: function () { if (io) io.unobserve(el); } }; });
562
- function showAll() { els.forEach(function (el) { el.classList.add('is-in'); }); if (io) io.disconnect(); }
576
+ var els = [], io, calm = still.matches;
577
+ each(ctx, '.gw-reveal', 'reveal', function (el) {
578
+ els.push(el);
579
+ return { stop: function () { if (io) io.unobserve(el); el.style.transitionDelay = ''; } };
580
+ });
581
+ function show(el) {
582
+ if (el.classList.contains('is-in')) return;
583
+ el.classList.add('is-in');
584
+ if (!calm) [].forEach.call(el.querySelectorAll('[data-gw-count]'), countUp);
585
+ }
563
586
  if (!els.length) return;
564
- if (!window.IntersectionObserver || mq('(prefers-reduced-motion: reduce)').matches) return showAll();
587
+ if (!window.IntersectionObserver || calm) return els.forEach(show);
588
+ els.forEach(function (el) {
589
+ var i = [].filter.call(el.parentNode.children, function (c) { return c.classList.contains('gw-reveal'); }).indexOf(el);
590
+ if (i > 0) el.style.transitionDelay = Math.min(i * REVEAL.stagger, REVEAL.staggerMax) + 'ms';
591
+ });
565
592
  io = new IntersectionObserver(function (list) {
566
- list.forEach(function (en) { if (en.isIntersecting) { en.target.classList.add('is-in'); io.unobserve(en.target); } });
567
- }, { threshold: 0.15 });
593
+ list.forEach(function (en) { if (en.isIntersecting) { show(en.target); io.unobserve(en.target); } });
594
+ }, { threshold: REVEAL.threshold, rootMargin: REVEAL.margin });
568
595
  els.forEach(function (el) { io.observe(el); });
569
- setTimeout(showAll, 2500);
596
+ setTimeout(function () { els.forEach(show); io.disconnect(); }, REVEAL.failsafe);
570
597
  }
571
598
 
572
599
  // Header: .is-scrolled past 8px, read once per frame (one scroll listener for every header).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gemboss/website-ui",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "The GemBoss website kit: tokens, header, footer, sections and the rules (AGENTS.md) that GemCommerce public websites share, plus a CLI that checks a site, reports kit gaps and upgrades.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/system.json CHANGED
@@ -78,6 +78,16 @@
78
78
  "dot": "rgba(255, 255, 255, 0.1)",
79
79
  "ink": "#ffffff"
80
80
  },
81
+ "announce": {
82
+ "$comment": "The announcement bar (0.12.0): one sentence over the header, as gemboss.ai draws it, the same in light and dark. bg its ground, ink its words, line its hairline at the bottom; tag and tag-ink the small pill before the sentence; link the link's words and link-line their underline. A brand sets its own bar here.",
83
+ "bg": "#171a38",
84
+ "ink": "#b9beda",
85
+ "line": "rgba(255, 255, 255, 0.06)",
86
+ "tag": "rgba(122, 132, 255, 0.2)",
87
+ "tag-ink": "#a7aeff",
88
+ "link": "#ffffff",
89
+ "link-line": "rgba(255, 255, 255, 0.28)"
90
+ },
81
91
  "roles": {
82
92
  "action": "The thing you click: ink, or the brand's accent when the site chose the accent action profile (profiles.action).",
83
93
  "accent": "Links, focus rings, active state, a chart line, the 8px label square; the primary button only through the accent action profile. Never body text, never words in a heading, never a section band.",
@@ -85,7 +95,8 @@
85
95
  "line-control": "The border of a form control (a field, a checkbox, a radio), 0.6.0: it must reach 3:1 on bg, bg-soft and surface, light and dark, where line and line-strong are hairlines that do not. Like the dashboard's --gemboss-field-line.",
86
96
  "wash": "The hero wash and illustrations only.",
87
97
  "scrim": "The veil over the page while a mega menu is open (0.11.0): a dim and a 4px blur, as gemboss.ai.",
88
- "stage": "The ground of a mega menu's pictures and nothing else (0.11.0)."
98
+ "stage": "The ground of a mega menu's pictures and nothing else (0.11.0).",
99
+ "announce": "The announcement bar over the header and nothing else (0.12.0)."
89
100
  }
90
101
  },
91
102
  "radius": {
@@ -113,13 +124,16 @@
113
124
  "wordmark": "clamp(40px, 9vw, 128px)",
114
125
  "$comment": "wordmark (0.7.0): the footer's last line on gemboss.ai, the brand's name, faint, at the end of the page. Decoration only, never a heading. Weight 800 (0.10.0, as gemboss.ai draws it): the one use of 800 in the kit.",
115
126
  "weight": 600,
116
- "tracking": "-0.03em"
127
+ "tracking": "-0.03em",
128
+ "h1_leading": 1.1,
129
+ "$comment_leading": "1.1 and 1.06 (0.12.0, gemboss.ai's): at 1.02 the descenders of a first line touched the capitals of the second (Chris, 15/10/2026).",
130
+ "h2_leading": 1.06
117
131
  },
118
132
  "heading": { "weight": 650, "tracking": "-0.022em", "line_height": 1.08 },
119
133
  "subhead": { "weight": 600 },
120
134
  "body": { "size": 18, "line_height": 1.6 },
121
- "label": { "size": 13, "weight": 500, "case": "sentence", "$comment": "muted, led by the 8px accent square; never uppercase tracking" },
122
- "button": { "size": 14, "weight": 600, "case": "sentence" }
135
+ "label": { "size": 13, "weight": 500, "case": "sentence", "$comment": "muted, led by the 8px square in ink (0.12.0, as gemboss.ai; the input --gw-label-mark paints it another colour); never uppercase tracking" },
136
+ "button": { "size": 14, "weight": 600, "case": "sentence", "family": "Inter", "$comment": "family (0.12.0): a button names the brand face alone, as gemboss.ai does. A glyph the face does not carry (the arrow → is not in Inter's web subset) is then drawn by the system's own fallback, the width gemboss.ai's buttons have (with the full stack it is a different face, 1px narrower, and a pair of buttons fits where gemboss.ai wraps it)." }
123
137
  },
124
138
  "space": {
125
139
  "scale": [0, 2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 120],
@@ -168,6 +182,16 @@
168
182
  "properties": ["transform", "opacity"],
169
183
  "ease": "cubic-bezier(.22,1,.36,1)",
170
184
  "reveal_ms": 700,
185
+ "reveal": {
186
+ "$comment": "Scroll reveal (0.12.0, as gemboss.ai): a .gw-reveal rises distance px and fades in over reveal_ms when threshold of it is in view, the bottom margin of the view taken off; each one after the first among its .gw-reveal siblings waits stagger_ms more (at most stagger_max_ms); all are shown failsafe_ms after the script starts. A [data-gw-count] number inside counts up from 0 over count_ms when its reveal shows. The script's numbers are held equal to these by the tests.",
187
+ "distance": 20,
188
+ "threshold": 0.12,
189
+ "margin": "0px 0px -6% 0px",
190
+ "stagger_ms": 70,
191
+ "stagger_max_ms": 380,
192
+ "failsafe_ms": 1600,
193
+ "count_ms": 1100
194
+ },
171
195
  "spin_ms": 700,
172
196
  "$comment": "Animate transform and opacity only. Everything is still under prefers-reduced-motion (a busy button's ring turns once every spin_ms, as the dashboard's spinner, and stands still as an ellipsis). One exception, the spotlight (spot): its light is one painted layer of one element, so its radius may grow.",
173
197
  "spot": {
@@ -11,7 +11,8 @@
11
11
  the panel's (--gw-mega-w, --gw-mega-pv; 1000 and 320 when it says nothing), its columns too (--gw-mega-cols, 3).
12
12
  Limits: at most five top-level items, one .gw-btn (the primary action), one second text action (.gw-header__link).
13
13
  Each panel: two or three columns of four or five rows, at most one card, three links in its strip. The announcement
14
- bar is optional: one sentence.
14
+ bar is optional: one sentence, one link, a small pill before it if you like (.gw-announce__tag, one word); its
15
+ colours are the brand's (system.json color.announce), the same in light and dark.
15
16
  Two options (0.8.0):
16
17
  - The mobile menu drills down (data-gw-menu="drill" on #gw-mobile-menu, as gemboss.ai's phone menu): the top level
17
18
  lists the sections, a tap on one shows its level alone with Back and its title, and the actions stay in a bar at
@@ -25,7 +26,7 @@
25
26
  never be translated. Put the word for "Language" in the page's language in data-gw-label-language.
26
27
  Replace every [bracketed] placeholder; delete what you do not use.
27
28
  -->
28
- <div class="gw-announce" role="region" aria-label="Announcement">[One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
29
+ <div class="gw-announce" role="region" aria-label="Announcement"><span class="gw-announce__tag">[New]</span> [One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
29
30
  <header class="gw-header" data-gw-header>
30
31
  <div class="gw-header__bar">
31
32
  <a class="gw-brand" href="/"><img class="gw-brand__mark" src="[/logo.svg]" alt="" width="34" height="34"><span class="gw-brand__name">[Brand]</span></a>
@@ -26,7 +26,7 @@
26
26
  <body class="gw-page">
27
27
  <a class="gw-skip" href="#main">Skip to content</a>
28
28
 
29
- <div class="gw-announce" role="region" aria-label="Announcement">[One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
29
+ <div class="gw-announce" role="region" aria-label="Announcement"><span class="gw-announce__tag">[New]</span> [One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
30
30
  <header class="gw-header" data-gw-header>
31
31
  <div class="gw-header__bar">
32
32
  <a class="gw-brand" href="/"><img class="gw-brand__mark" src="[/logo.svg]" alt="" width="34" height="34"><span class="gw-brand__name">[Brand]</span></a>
package/website.css CHANGED
@@ -1,9 +1,10 @@
1
- /*! @gemboss/website-ui 0.11.0 | generated by scripts/build.mjs from the files in css/. Edit those and not this file */
1
+ /*! @gemboss/website-ui 0.12.0 | generated by scripts/build.mjs from the files in css/. Edit those and not this file */
2
2
  /* @gemboss/website-ui tokens. GENERATED from system.json by scripts/build.mjs: do not edit by hand. */
3
3
  :root {
4
4
  color-scheme: light;
5
5
  --gw-font: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
6
6
  --gw-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
7
+ --gw-font-action: Inter;
7
8
  --gw-text-11: 11px;
8
9
  --gw-text-12: 12px;
9
10
  --gw-text-13: 13px;
@@ -19,6 +20,8 @@
19
20
  --gw-display-wordmark: clamp(40px, 9vw, 128px);
20
21
  --gw-display-weight: 600;
21
22
  --gw-display-tracking: -0.03em;
23
+ --gw-display-h1-leading: 1.1;
24
+ --gw-display-h2-leading: 1.06;
22
25
  --gw-heading-weight: 650;
23
26
  --gw-heading-tracking: -0.022em;
24
27
  --gw-heading-leading: 1.08;
@@ -71,6 +74,7 @@
71
74
  --gw-tap-min: 44px;
72
75
  --gw-ease: cubic-bezier(.22,1,.36,1);
73
76
  --gw-reveal-ms: 700ms;
77
+ --gw-reveal-distance: 20px;
74
78
  --gw-spin-ms: 700ms;
75
79
  --gw-spot-radius: 260px;
76
80
  --gw-spot-radius-action: 120px;
@@ -89,6 +93,13 @@
89
93
  --gw-stage-bottom: #0d0f2e;
90
94
  --gw-stage-dot: rgba(255, 255, 255, 0.1);
91
95
  --gw-stage-ink: #ffffff;
96
+ --gw-announce-bg: #171a38;
97
+ --gw-announce-ink: #b9beda;
98
+ --gw-announce-line: rgba(255, 255, 255, 0.06);
99
+ --gw-announce-tag: rgba(122, 132, 255, 0.2);
100
+ --gw-announce-tag-ink: #a7aeff;
101
+ --gw-announce-link: #ffffff;
102
+ --gw-announce-link-line: rgba(255, 255, 255, 0.28);
92
103
  /* accent primitives: the only colours a brand overrides (docs/theming.md) */
93
104
  --gw-accent-light: #5661f5;
94
105
  --gw-accent-strong-light: #3a44de;
@@ -305,33 +316,34 @@ h1, h2, h3, h4, h5, h6, .gw-h1, .gw-h2, .gw-h3, .gw-h4 {
305
316
  text-wrap: balance;
306
317
  }
307
318
  h1, .gw-h1, h2, .gw-h2 { font-weight: var(--gw-display-weight); letter-spacing: var(--gw-display-tracking); }
308
- h1, .gw-h1 { font-size: var(--gw-display-h1); }
309
- h2, .gw-h2 { font-size: var(--gw-display-h2); }
310
- h3, .gw-h3 { font-size: 24px; }
319
+ h1, .gw-h1 { font-size: var(--gw-display-h1); line-height: var(--gw-display-h1-leading); }
320
+ h2, .gw-h2 { font-size: var(--gw-display-h2); line-height: var(--gw-display-h2-leading); }
321
+ /* h3 (0.12.0): 20px/600, as every h3 on gemboss.ai */
322
+ h3, .gw-h3 { font-size: 20px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.2; }
311
323
  h4, .gw-h4 { font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
312
324
  h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
313
325
 
314
- .gw-lead { font-size: 18px; line-height: 1.55; color: var(--gw-muted); max-width: 60ch; }
326
+ .gw-lead { font-size: 18px; line-height: 1.55; color: var(--gw-muted); max-width: 58ch; }
315
327
  .gw-note { font-size: 14px; color: var(--gw-muted); }
316
328
  .gw-dim { color: var(--gw-muted); }
317
329
 
318
- /* the label above a heading: sentence case, muted, led by the 8px accent square */
330
+ /* the label above a heading: sentence case, muted, led by the 8px square, ink by default (0.12.0, as gemboss.ai). The
331
+ input --gw-label-mark paints the square another colour (a product's own, say): set it on the label or on anything
332
+ around it, e.g. style="--gw-label-mark: var(--gw-accent)". */
319
333
  .gw-label {
320
334
  display: inline-flex;
321
335
  align-items: center;
322
336
  gap: 8px;
323
337
  font-size: 13px;
324
338
  font-weight: 500;
325
- line-height: 1.4;
326
339
  color: var(--gw-muted);
327
340
  }
328
341
  .gw-label::before {
329
342
  content: "";
330
- flex: none;
331
343
  width: 8px;
332
344
  height: 8px;
333
- border-radius: var(--gw-radius-0);
334
- background: var(--gw-accent);
345
+ border-radius: var(--gw-radius-2);
346
+ background: var(--gw-label-mark, var(--gw-ink));
335
347
  }
336
348
 
337
349
  /* links inside running text (and anywhere in .gw-prose) get a visible link style; everything else opts in with .gw-link */
@@ -351,19 +363,22 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
351
363
  :focus-visible { border-radius: var(--gw-radius-4); outline: 2px solid var(--gw-accent); outline-offset: 3px; }
352
364
  [id] { scroll-margin-top: calc(var(--gw-header-height) + 16px); }
353
365
 
366
+ /* the skip link (0.12.0, gemboss.ai's): the accent tab in the top left corner, shown while it has the focus */
354
367
  .gw-skip {
355
368
  position: absolute;
356
369
  left: -9999px;
357
- top: 8px;
358
- z-index: 100;
359
- padding: 12px 16px;
360
- border-radius: var(--gw-radius-action);
361
- background: var(--gw-action);
362
- color: var(--gw-on-action);
370
+ top: 0;
371
+ z-index: 200;
372
+ padding: 12px 20px;
373
+ border-radius: 0 0 var(--gw-radius-card) 0;
374
+ background: var(--gw-accent);
375
+ color: var(--gw-on-accent);
376
+ box-shadow: var(--gw-shadow);
363
377
  font-size: 14px;
364
378
  font-weight: 600;
365
379
  }
366
- .gw-skip:focus { left: 16px; }
380
+ /* its corner again with the focus: a page's own :focus-visible corners, loaded after the kit, must not round the tab */
381
+ .gw-skip:focus { left: 0; border-radius: 0 0 var(--gw-radius-card) 0; }
367
382
 
368
383
  /* words for a screen reader only (0.7.0): " (opens in a new tab)" in a link with target="_blank" */
369
384
  .gw-visually-hidden {
@@ -377,10 +392,11 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
377
392
  border: 0;
378
393
  }
379
394
 
380
- /* scroll reveal: the script adds .is-in; the animation is a failsafe that shows it anyway after 2.5s */
395
+ /* scroll reveal: the script adds .is-in (system.json motion.reveal: when, the stagger, the failsafe); the animation is
396
+ a second failsafe, for a page whose script never ran, that shows it anyway after 2.5s */
381
397
  .gw-reveal {
382
398
  opacity: 0;
383
- transform: translateY(24px);
399
+ transform: translateY(var(--gw-reveal-distance));
384
400
  transition: opacity var(--gw-reveal-ms) var(--gw-ease), transform var(--gw-reveal-ms) var(--gw-ease);
385
401
  animation: gw-reveal-failsafe 1ms linear 2.5s forwards;
386
402
  }
@@ -406,15 +422,19 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
406
422
  @media (min-width: 768px) { .gw-section--tight { padding-block: 64px; } }
407
423
 
408
424
  /* label + h2 + one or two lines; the vertical flow of every text block in the kit */
409
- .gw-section__head { max-width: 660px; }
425
+ .gw-section__head { max-width: 760px; }
410
426
  .gw-section__head--center { margin-inline: auto; text-align: center; }
411
427
  .gw-section__head--center .gw-lead { margin-inline: auto; }
412
- .gw-section__head:not(:last-child) { margin-bottom: 40px; }
413
- @media (min-width: 768px) { .gw-section__head:not(:last-child) { margin-bottom: 48px; } }
428
+ /* what follows a head starts 40px under it, 48px from 768px (0.12.0): a top margin on the next thing, at no
429
+ specificity, so a block that brings its own (a FAQ's 40px, say) keeps it */
430
+ :where(.gw-section__head) + * { margin-top: 40px; }
431
+ @media (min-width: 768px) { :where(.gw-section__head) + * { margin-top: 48px; } }
414
432
 
415
433
  :where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-feature, .gw-plan) > * + * { margin-top: 16px; }
416
434
  :where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-plan) > :is(.gw-actions, .gw-segmented) { margin-top: 32px; }
417
435
  :where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-plan) > .gw-list--check { margin-top: 24px; }
436
+ /* a lead under a heading: 20px (0.12.0, as gemboss.ai); in a hero it keeps the 16px of the flow */
437
+ :where(.gw-section__head, .gw-split > :not(.gw-window), .gw-cta) > :where(* + .gw-lead) { margin-top: 20px; }
418
438
 
419
439
  /* a row of actions: one .gw-btn, then a secondary or a link */
420
440
  .gw-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
@@ -456,12 +476,12 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
456
476
 
457
477
  /* actions are rectangles: 40px, the action radius of the site's profile (8, 12 in soft), 14px/600, sentence case.
458
478
  The fill is the action colour: ink, or the brand's accent under data-gw-action="accent" (tokens.css). Never a
459
- gradient, a wash hue or a shadow. */
479
+ gradient or a wash hue. On hover (0.12.0, as gemboss.ai) the filled button rises 1px onto the rest shadow; the framed
480
+ and frameless ones only change their frame or tint. Phones: see "touch" below. */
460
481
  .gw-btn {
461
482
  position: relative;
462
483
  display: inline-flex;
463
484
  align-items: center;
464
- justify-content: center;
465
485
  gap: 8px;
466
486
  max-width: 100%;
467
487
  height: var(--gw-button-height);
@@ -470,26 +490,34 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
470
490
  border-radius: var(--gw-radius-action);
471
491
  background: var(--gw-action);
472
492
  color: var(--gw-on-action);
493
+ font-family: var(--gw-font-action);
473
494
  font-size: 14px;
474
495
  font-weight: 600;
475
- line-height: 1.4;
476
496
  letter-spacing: 0;
477
497
  text-align: center;
478
498
  white-space: nowrap;
479
499
  cursor: pointer;
500
+ transition: background 0.15s ease, border-color 0.15s ease;
480
501
  }
481
- .gw-btn:hover { background: var(--gw-action-hover); }
502
+ .gw-btn:hover { transform: translateY(-1px); box-shadow: var(--gw-shadow); }
482
503
  .gw-btn svg { flex: none; width: 16px; height: 16px; }
483
504
  .gw-btn--secondary { background: transparent; border-color: var(--gw-line-strong); color: var(--gw-ink); }
484
505
  .gw-btn--secondary:hover { background: transparent; border-color: var(--gw-ink); }
485
506
  .gw-btn--ghost { background: transparent; color: var(--gw-ink); }
486
507
  .gw-btn--ghost:hover { background: var(--gw-bg-soft); }
508
+ .gw-btn:is(.gw-btn--secondary, .gw-btn--ghost):hover { transform: none; box-shadow: none; }
509
+ /* an arrow after the label (→), nudged 3px on hover */
510
+ .gw-btn__arrow { letter-spacing: 0; transition: transform 0.18s ease; }
511
+ .gw-btn:hover .gw-btn__arrow { transform: translateX(3px); }
512
+
487
513
  .gw-btn--on-night { background: var(--gw-on-night); color: var(--gw-night); }
488
514
  .gw-btn--on-night:hover { background: var(--gw-on-night); opacity: 0.88; }
489
515
  .gw-btn--sm { height: 36px; padding: 0 16px; }
490
516
  .gw-btn--sm::after { content: ""; position: absolute; top: -6px; right: 0; bottom: -6px; left: 0; }
491
517
  .gw-btn--lg { height: 48px; padding: 0 24px; font-size: 16px; }
492
- .gw-btn--block { display: flex; width: 100%; }
518
+ /* a button wider than its label keeps the label at its start (as gemboss.ai), unless it is made wide on purpose: full
519
+ width (--block), a phone's row of actions, an inline form */
520
+ .gw-btn--block { display: flex; width: 100%; justify-content: center; }
493
521
  .gw-btn:disabled, .gw-btn[aria-disabled="true"] { opacity: 0.48; cursor: not-allowed; pointer-events: none; }
494
522
  /* pressed: the ink one step darker (the hover ink, as the dashboard's primary), nothing moves; the frameless variants
495
523
  take one more step of tint */
@@ -788,16 +816,21 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
788
816
  .gw-faq__item[open] > summary::after { transform: rotate(45deg); }
789
817
  .gw-faq__item > :not(summary) { margin: 0 0 20px; max-width: 68ch; color: var(--gw-muted); font-size: 16px; }
790
818
 
791
- /* touch: every control is at least 44px tall, labels may wrap */
792
- @media (max-width: 768px), (pointer: coarse) {
819
+ /* touch: every control is at least 44px tall. A button up to 768px (0.12.0, as gemboss.ai: by the width, not the
820
+ pointer), its label wrapping (and tighter) from 480px down in the page's <main>, so a long one never pushes the page
821
+ sideways (a header or an overlay outside <main> keeps its one line) */
822
+ @media (max-width: 768px) {
793
823
  .gw-btn, .gw-btn--sm, .gw-btn--lg {
794
824
  height: auto;
795
825
  min-height: var(--gw-tap-min);
796
826
  padding-block: 8px;
797
- white-space: normal;
798
- text-wrap: balance;
799
827
  }
800
828
  .gw-btn--lg { min-height: 48px; }
829
+ }
830
+ @media (max-width: 480px) {
831
+ :where(main) .gw-btn { white-space: normal; line-height: 1.35; }
832
+ }
833
+ @media (max-width: 768px), (pointer: coarse) {
801
834
  :is(a, button, label).gw-chip { min-height: var(--gw-tap-min); }
802
835
  .gw-segmented > button { height: auto; min-height: var(--gw-tap-min); padding-block: 8px; white-space: normal; text-wrap: balance; }
803
836
  }
@@ -805,16 +838,50 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
805
838
  /* header: announcement, a flat 64px bar with one hairline, mega menu, mobile menu up to 1080px.
806
839
  Not a floating pill: no radius, no blur, no shadow on the bar. Nothing tucks under it. A brand may choose the one
807
840
  other shape, .gw-header--float (an inset card from 1081px, at the end of this file). */
841
+ /* the announcement bar (0.12.0, gemboss.ai's): one sentence on its own ground over the header, the same in both themes
842
+ (system.json color.announce). A small pill may lead it (.gw-announce__tag); its link is light, underlined by a hairline
843
+ that brightens on hover. From 520px down, 12px words and a slimmer bar. */
808
844
  .gw-announce {
809
- padding: 8px var(--gw-gutter);
810
- border-bottom: 1px solid var(--gw-line);
811
- background: var(--gw-bg-soft);
812
- color: var(--gw-ink-2);
845
+ position: relative;
846
+ z-index: 60;
847
+ padding: 12px 16px;
848
+ border-bottom: 1px solid var(--gw-announce-line);
849
+ background: var(--gw-announce-bg);
850
+ color: var(--gw-announce-ink);
813
851
  font-size: 13px;
852
+ font-weight: 500;
814
853
  line-height: 1.5;
854
+ letter-spacing: 0.005em;
815
855
  text-align: center;
816
856
  }
817
- .gw-announce a { color: var(--gw-ink); font-weight: 600; text-decoration: underline; text-underline-offset: 4px; }
857
+ /* 2.5px above and below, as gemboss.ai draws the pill (21.5px tall): the one half pixel in the kit */
858
+ .gw-announce__tag {
859
+ display: inline-block;
860
+ margin-right: 12px;
861
+ padding: 2.5px 8px;
862
+ border-radius: var(--gw-radius-chip);
863
+ background: var(--gw-announce-tag);
864
+ color: var(--gw-announce-tag-ink);
865
+ font-size: 11px;
866
+ font-weight: 700;
867
+ letter-spacing: 0.09em;
868
+ vertical-align: 1px;
869
+ }
870
+ .gw-announce a {
871
+ margin-left: 8px;
872
+ padding-bottom: 2px;
873
+ border-bottom: 1px solid var(--gw-announce-link-line);
874
+ color: var(--gw-announce-link);
875
+ font-weight: 600;
876
+ text-decoration: none;
877
+ white-space: nowrap;
878
+ transition: border-color 0.18s;
879
+ }
880
+ .gw-announce a:hover { border-bottom-color: var(--gw-announce-link); }
881
+ @media (max-width: 520px) {
882
+ .gw-announce { padding: 8px 12px; font-size: 12px; }
883
+ .gw-announce__tag { margin-right: 6px; }
884
+ }
818
885
 
819
886
  .gw-header {
820
887
  position: sticky;
@@ -1753,7 +1820,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1753
1820
  .gw-hero > .gw-wrap > .gw-window { margin-top: 64px; }
1754
1821
  }
1755
1822
  @media (max-width: 479px) {
1756
- :is(.gw-hero, .gw-cta) .gw-actions > .gw-btn { flex: 1 1 100%; }
1823
+ :is(.gw-hero, .gw-cta) .gw-actions > .gw-btn { flex: 1 1 100%; justify-content: center; }
1757
1824
  }
1758
1825
 
1759
1826
  /* logos: a proof strip of real logos or wordmarks */
@@ -2407,7 +2474,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
2407
2474
  room; below 480px, and in a box too narrow for both, the field and the button each take a full line. */
2408
2475
  .gw-form-inline { display: flex; flex-wrap: wrap; gap: 8px; }
2409
2476
  .gw-form-inline > :not(.gw-btn) { flex: 999 1 160px; min-width: 0; }
2410
- .gw-form-inline > .gw-btn { flex: 1 0 auto; }
2477
+ .gw-form-inline > .gw-btn { flex: 1 0 auto; justify-content: center; }
2411
2478
  @media (max-width: 479px) {
2412
2479
  .gw-form-inline > :not(.gw-btn) { flex-basis: 100%; }
2413
2480
  }