@gemboss/website-ui 0.8.0 → 0.10.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/website.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! @gemboss/website-ui 0.8.0 | generated by scripts/build.mjs from the files in css/. Edit those and not this file */
1
+ /*! @gemboss/website-ui 0.10.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;
@@ -70,6 +70,13 @@
70
70
  --gw-ease: cubic-bezier(.22,1,.36,1);
71
71
  --gw-reveal-ms: 700ms;
72
72
  --gw-spin-ms: 700ms;
73
+ --gw-spot-radius: 260px;
74
+ --gw-spot-radius-action: 120px;
75
+ --gw-spot-strength: 22%;
76
+ --gw-spot-strength-action: 38%;
77
+ --gw-spot-strength-stage: 40%;
78
+ --gw-spot-ms: 550ms;
79
+ --gw-spot-ease: cubic-bezier(.16,1,.3,1);
73
80
  --gw-wash-pink: #ff6f5e;
74
81
  --gw-wash-violet: #7c8af7;
75
82
  --gw-wash-sky: #43b0ff;
@@ -88,7 +95,7 @@
88
95
  --gw-surface: #ffffff;
89
96
  --gw-ink: #1b1c1e;
90
97
  --gw-ink-2: #3b3d42;
91
- --gw-muted: #686b71;
98
+ --gw-muted: #6a6d73;
92
99
  --gw-line: #e5e7ea;
93
100
  --gw-line-strong: #d5d8dd;
94
101
  --gw-line-control: #85888e;
@@ -180,7 +187,7 @@
180
187
  --gw-surface: #ffffff;
181
188
  --gw-ink: #1b1c1e;
182
189
  --gw-ink-2: #3b3d42;
183
- --gw-muted: #686b71;
190
+ --gw-muted: #6a6d73;
184
191
  --gw-line: #e5e7ea;
185
192
  --gw-line-strong: #d5d8dd;
186
193
  --gw-line-control: #85888e;
@@ -1215,22 +1222,38 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1215
1222
  .gw-header__actions > .gw-lang { display: block; }
1216
1223
  }
1217
1224
 
1225
+ /* ---- 0.10.0: the theme toggle's two icons ---------------------------------------------------------------------------
1226
+ A toggle may hold two icons: .gw-theme-toggle__moon, shown in light, and .gw-theme-toggle__sun, shown in dark, as
1227
+ gemboss.ai draws them. The theme in force picks one: data-theme on <html>, else the system's. A toggle with one plain
1228
+ <svg> stays as it is. */
1229
+ .gw-theme-toggle__sun { display: none; }
1230
+ :root[data-theme="dark"] .gw-theme-toggle__sun { display: block; }
1231
+ :root[data-theme="dark"] .gw-theme-toggle__moon { display: none; }
1232
+ @media (prefers-color-scheme: dark) {
1233
+ :root:not([data-theme="light"]) .gw-theme-toggle__sun { display: block; }
1234
+ :root:not([data-theme="light"]) .gw-theme-toggle__moon { display: none; }
1235
+ }
1236
+
1218
1237
  /* footer: brand block, link columns, bottom row. One hairline on top, white like the page. */
1219
1238
  .gw-footer {
1220
- padding-block: 64px 40px;
1239
+ padding-block: 64px 32px;
1221
1240
  border-top: 1px solid var(--gw-line);
1222
- background: var(--gw-bg);
1241
+ background: transparent;
1223
1242
  color: var(--gw-ink);
1224
1243
  font-size: 14px;
1225
1244
  }
1226
1245
  .gw-footer__grid {
1227
1246
  display: grid;
1228
1247
  grid-template-columns: repeat(2, minmax(0, 1fr));
1229
- gap: 32px 24px;
1230
- }
1231
- .gw-footer__brand { grid-column: 1 / -1; max-width: 320px; }
1232
- .gw-footer__brand p { margin-top: 16px; color: var(--gw-muted); }
1233
- .gw-footer__heading { margin: 0 0 8px; color: var(--gw-ink); font-size: 13px; font-weight: 600; letter-spacing: 0; }
1248
+ gap: 32px;
1249
+ }
1250
+ .gw-footer__brand p { max-width: 30ch; margin-top: 16px; color: var(--gw-muted); line-height: 22px; }
1251
+ .gw-footer__brand .gw-brand { display: flex; min-height: 34px; font-family: var(--gw-font); line-height: 1.6; }
1252
+ .gw-footer__brand .gw-brand__mark { width: 34px; height: 34px; border-radius: var(--gw-radius-8); }
1253
+ /* the brand's name, the column headings and the wordmark are set in the kit's face (--gw-font), whatever the page
1254
+ does to its body or its h2 (a page in Chinese puts PingFang first; gemboss.ai keeps Inter for these three); a
1255
+ heading is balanced, like gemboss.ai's main pages */
1256
+ .gw-footer__heading { margin: 0 0 12px; color: var(--gw-ink); font-family: var(--gw-font); font-size: 13px; font-weight: 600; letter-spacing: 0; line-height: 1.6; text-wrap: balance; }
1234
1257
  /* an optional newsletter form in the brand block (0.6.0): label, the inline row (field and button), a note on what the
1235
1258
  reader agrees to; only when the form is wired to a list that keeps the address */
1236
1259
  .gw-footer__newsletter { margin-top: 24px; }
@@ -1243,33 +1266,35 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1243
1266
  flex-wrap: wrap;
1244
1267
  align-items: center;
1245
1268
  justify-content: space-between;
1246
- gap: 8px 24px;
1247
- margin-top: 48px;
1269
+ gap: 12px 24px;
1270
+ margin-top: 32px;
1248
1271
  padding-top: 24px;
1249
1272
  border-top: 1px solid var(--gw-line);
1250
1273
  color: var(--gw-muted);
1251
1274
  font-size: 13px;
1252
1275
  }
1253
1276
  .gw-footer__bottom:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
1254
- .gw-footer__bottom ul { display: flex; flex-wrap: wrap; gap: 0 16px; margin: 0; padding: 0; list-style: none; }
1277
+ .gw-footer__bottom ul { display: flex; flex-wrap: wrap; gap: 16px; margin: 0; padding: 0; list-style: none; }
1255
1278
  .gw-footer__bottom a {
1256
1279
  display: inline-flex;
1257
1280
  align-items: center;
1258
1281
  justify-content: center;
1259
- min-width: 24px;
1260
- min-height: 24px;
1261
1282
  color: var(--gw-muted);
1262
1283
  }
1263
1284
  .gw-footer__bottom a:hover { color: var(--gw-ink); }
1264
- @media (min-width: 768px) {
1265
- .gw-footer__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1285
+ @media (max-width: 620px) {
1286
+ .gw-footer__grid { gap: 24px 20px; }
1287
+ .gw-footer__brand { grid-column: 1 / -1; }
1266
1288
  }
1267
- @media (min-width: 1080px) {
1268
- .gw-footer__grid { grid-auto-flow: column; grid-template-columns: minmax(0, 1.6fr); grid-auto-columns: minmax(0, 1fr); }
1269
- .gw-footer__brand { grid-column: auto; }
1289
+ @media (min-width: 1081px) {
1290
+ .gw-footer__grid { grid-auto-flow: column; grid-template-columns: minmax(0, 1.6fr); grid-auto-columns: minmax(0, 1fr); gap: 32px 24px; }
1270
1291
  }
1271
1292
  @media (max-width: 768px), (pointer: coarse) {
1293
+ .gw-footer__brand .gw-brand { min-height: var(--gw-tap-min); }
1294
+ .gw-footer__col a { display: flex; align-items: center; min-height: var(--gw-tap-min); padding-block: 0; }
1272
1295
  .gw-footer__bottom a { min-height: var(--gw-tap-min); }
1296
+ .gw-footer__bottom ul:last-child a { min-width: var(--gw-tap-min); }
1297
+ .gw-footer__bottom ul:not(:last-child) { gap: 8px 16px; }
1273
1298
  }
1274
1299
 
1275
1300
  /* ---- 0.7.0: the footer as gemboss.ai has it (issue #71) ---------------------------------------------------------------
@@ -1283,16 +1308,14 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1283
1308
  touch. */
1284
1309
  .gw-footer__ask { margin-top: 24px; }
1285
1310
  /* the row may run wider than the brand's sentence (four names on one line at 390px) */
1286
- .gw-footer__brand:has(> .gw-footer__ask) { max-width: none; }
1287
- .gw-footer__brand:has(> .gw-footer__ask) > :not(.gw-footer__ask) { max-width: 320px; }
1288
- .gw-footer__ask > .gw-footer__ask-label { margin: 0; color: var(--gw-muted); font-size: 13px; }
1311
+ .gw-footer__ask > .gw-footer__ask-label { margin: 0; color: var(--gw-muted); font-size: 13px; line-height: 22px; }
1289
1312
  .gw-footer__ask > ul { display: flex; flex-wrap: wrap; gap: 0 16px; margin: 4px 0 0; padding: 0; list-style: none; }
1290
1313
  .gw-footer__ask a { display: block; padding-block: 4px; color: var(--gw-ink); line-height: 20px; }
1291
- .gw-footer__ask a:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }
1314
+ .gw-footer__ask a:hover { text-decoration: underline; }
1292
1315
 
1293
1316
  /* the mark after a link that leaves the site (↗, drawn), aria-hidden; the words "(opens in a new tab)" are a separate
1294
1317
  .gw-visually-hidden span, only when the link has target="_blank" */
1295
- .gw-footer__ext {
1318
+ .gw-footer__ext:empty {
1296
1319
  display: inline-block;
1297
1320
  width: 12px;
1298
1321
  height: 12px;
@@ -1302,6 +1325,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1302
1325
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3.5 8.5l5-5M4.5 3.5h4v4' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
1303
1326
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3.5 8.5l5-5M4.5 3.5h4v4' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
1304
1327
  }
1328
+ .gw-footer__ext:not(:empty) { color: var(--gw-muted); font-weight: 400; }
1305
1329
 
1306
1330
  /* Folds: <details class="gw-footer__fold"> inside a .gw-footer__col (or a .gw-footer__family), its <summary> holding the
1307
1331
  .gw-footer__heading, then the <ul>. Below 768px each is a 52px row with a chevron, folded unless the markup says
@@ -1310,48 +1334,52 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1310
1334
  (::details-content; website.js also sets `open` there, for browsers without it). */
1311
1335
  .gw-footer__fold > summary { list-style: none; }
1312
1336
  .gw-footer__fold > summary::-webkit-details-marker { display: none; }
1313
- .gw-footer__fold .gw-footer__heading { margin: 0; line-height: 20px; }
1337
+ .gw-footer__fold .gw-footer__heading { margin: 0; }
1314
1338
  .gw-footer__fold > ul { margin: 0; padding: 0; list-style: none; }
1315
1339
  .gw-footer__col > .gw-footer__fold a { line-height: 20px; }
1316
- @media (min-width: 768px) {
1340
+ @media (min-width: 621px) {
1317
1341
  .gw-footer__fold > summary { display: contents; }
1318
1342
  .gw-footer__col > .gw-footer__fold .gw-footer__heading { margin-bottom: 12px; }
1319
1343
  }
1320
- @media (min-width: 768px) {
1344
+ @media (min-width: 621px) {
1321
1345
  .gw-footer__fold::details-content { content-visibility: visible; }
1322
1346
  }
1323
- @media (max-width: 767px) {
1347
+ @media (max-width: 620px) {
1324
1348
  .gw-footer__grid:has(> .gw-footer__col > .gw-footer__fold) { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
1325
1349
  .gw-footer__grid:has(> .gw-footer__col > .gw-footer__fold) > .gw-footer__brand { padding-bottom: 24px; }
1326
- .gw-footer__col:has(> .gw-footer__fold),
1350
+ /* a column's hairline on top, the last column's below too, a family row's below (gemboss.ai's order: a focused
1351
+ row's ring passes under the next row's line) */
1352
+ .gw-footer__col:has(> .gw-footer__fold) { border-top: 1px solid var(--gw-line); }
1353
+ .gw-footer__col:has(> .gw-footer__fold):last-child,
1327
1354
  .gw-footer__family { border-bottom: 1px solid var(--gw-line); }
1328
- .gw-footer__brand + .gw-footer__col:has(> .gw-footer__fold) { border-top: 1px solid var(--gw-line); }
1329
1355
  .gw-footer__fold > summary {
1330
1356
  display: flex;
1331
1357
  align-items: center;
1332
1358
  justify-content: space-between;
1333
1359
  gap: 16px;
1334
- min-height: var(--gw-tap-min);
1335
- padding-block: 16px;
1360
+ min-height: 52px;
1361
+ padding: 0;
1336
1362
  cursor: pointer;
1337
1363
  }
1338
1364
  .gw-footer__fold > summary::after {
1339
1365
  content: "";
1340
1366
  flex: none;
1341
- width: 16px;
1342
- height: 16px;
1343
- background: var(--gw-muted);
1344
- -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.5 5.5L8 11l5.5-5.5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
1345
- mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.5 5.5L8 11l5.5-5.5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
1346
- transition: transform 0.18s var(--gw-ease);
1367
+ width: 8px;
1368
+ height: 8px;
1369
+ margin-right: 4px;
1370
+ border-right: 2px solid var(--gw-muted);
1371
+ border-bottom: 2px solid var(--gw-muted);
1372
+ rotate: 45deg;
1373
+ translate: 0 -2px;
1347
1374
  }
1348
- .gw-footer__fold[open] > summary::after { transform: rotate(180deg); }
1375
+ .gw-footer__fold[open] > summary::after { rotate: 225deg; translate: 0 2px; }
1376
+ .gw-footer__fold > summary:focus-visible { outline: 2px solid var(--gw-accent); outline-offset: 2px; border-radius: var(--gw-radius-6); }
1349
1377
  .gw-footer__fold > ul { padding-bottom: 12px; }
1350
1378
  .gw-footer__col > .gw-footer__fold a { display: flex; align-items: center; min-height: var(--gw-tap-min); }
1351
1379
  }
1352
1380
  /* desktop: the brand's track is as wide as gemboss.ai draws it beside five columns (1.2 parts of 6.2, after five 24px
1353
1381
  gaps), whatever the number of columns, so a family row can line up with it; the columns share the rest */
1354
- @media (min-width: 1080px) {
1382
+ @media (min-width: 1081px) {
1355
1383
  .gw-footer__grid:has(> .gw-footer__col > .gw-footer__fold) { grid-template-columns: minmax(0, calc((100% - 120px) * 6 / 31)); }
1356
1384
  }
1357
1385
 
@@ -1361,6 +1389,8 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1361
1389
  the items start where the first column starts. */
1362
1390
  .gw-footer__family .gw-footer__heading { color: var(--gw-muted); font-weight: 500; }
1363
1391
  .gw-footer__family .gw-footer__fold > ul { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
1392
+ .gw-footer__family .gw-footer__fold > ul > li { display: flex; }
1393
+ .gw-footer__family .gw-footer__fold > ul > li > a { flex: 1 1 auto; }
1364
1394
  .gw-footer__family a {
1365
1395
  display: flex;
1366
1396
  flex-direction: column;
@@ -1374,14 +1404,18 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1374
1404
  .gw-footer__family a:hover { background: var(--gw-bg-soft); }
1375
1405
  .gw-footer__family b { font-size: 14px; font-weight: 600; line-height: 20px; }
1376
1406
  .gw-footer__family i { color: var(--gw-muted); font-size: 13px; font-style: normal; line-height: 18px; }
1377
- @media (min-width: 768px) {
1407
+ @media (min-width: 621px) {
1378
1408
  .gw-footer__family { margin-top: 48px; padding-top: 32px; border-top: 1px solid var(--gw-line); }
1379
1409
  .gw-footer__family .gw-footer__heading { margin-bottom: 12px; }
1380
- .gw-footer__family .gw-footer__fold > ul { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px 16px; margin-left: -12px; }
1410
+ .gw-footer__family .gw-footer__fold > ul { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px 16px; }
1381
1411
  .gw-footer__family a { min-height: 0; padding: 8px 12px; }
1382
1412
  .gw-footer__family + .gw-footer__bottom { margin-top: 32px; }
1383
1413
  }
1384
- @media (min-width: 1080px) {
1414
+ @media (min-width: 621px) and (max-width: 768px) {
1415
+ .gw-footer__family a { min-height: var(--gw-tap-min); }
1416
+ }
1417
+ @media (min-width: 1081px) {
1418
+ .gw-footer__family .gw-footer__fold > ul { margin-left: -12px; }
1385
1419
  .gw-footer__family { display: flow-root; }
1386
1420
  .gw-footer__family .gw-footer__heading { float: left; width: calc((100% - 120px) * 6 / 31); margin: 8px 0 0; }
1387
1421
  .gw-footer__family .gw-footer__fold > ul { margin-left: calc((100% - 120px) * 6 / 31 + 12px); }
@@ -1389,7 +1423,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1389
1423
 
1390
1424
  /* The theme toggle in the bottom row (.gw-theme-toggle with data-gw-theme-toggle, wired like the header's): a 32px
1391
1425
  square with one hairline, its hit area 40px, 44px on touch. It pushes itself and the social links to the right.
1392
- Below 768px the copyright and the toggle share the first line, legal and social links the second. */
1426
+ Up to 620px the copyright and the toggle share the first line, legal and social links the second. */
1393
1427
  .gw-footer__toggle {
1394
1428
  position: relative;
1395
1429
  width: 32px;
@@ -1400,12 +1434,15 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1400
1434
  }
1401
1435
  .gw-footer__toggle::after { content: ""; position: absolute; inset: -5px; }
1402
1436
  .gw-footer__toggle svg { width: 18px; height: 18px; }
1437
+ /* on hover the hairline turns ink and the icon the accent, the ground stays (gemboss.ai's); colour only, 0.15s */
1438
+ .gw-footer__toggle { transition: border-color 0.15s, color 0.15s; }
1439
+ .gw-footer__toggle:hover { border-color: var(--gw-ink); background: var(--gw-surface); color: var(--gw-accent); }
1403
1440
  @media (max-width: 768px), (pointer: coarse) {
1404
1441
  .gw-footer__toggle { width: var(--gw-tap-min); height: var(--gw-tap-min); }
1405
1442
  }
1406
- @media (max-width: 767px) {
1407
- .gw-footer__bottom:has(> .gw-footer__toggle) { column-gap: 16px; }
1408
- .gw-footer__bottom:has(> .gw-footer__toggle) > :first-child { flex: 1 1 calc(100% - var(--gw-tap-min) - 16px); }
1443
+ @media (max-width: 620px) {
1444
+ .gw-footer__bottom:has(> .gw-footer__toggle) { gap: 8px 16px; }
1445
+ .gw-footer__bottom:has(> .gw-footer__toggle) > :first-child { flex: 1 1 calc(100% - 64px); }
1409
1446
  .gw-footer__bottom > .gw-footer__toggle { order: 1; margin: 0; }
1410
1447
  .gw-footer__bottom:has(> .gw-footer__toggle) > ul { order: 2; }
1411
1448
  .gw-footer__family + .gw-footer__bottom,
@@ -1415,39 +1452,37 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
1415
1452
  /* The wordmark: the brand's mark and name, large and faint, the footer's last line. Decoration (aria-hidden="true"):
1416
1453
  the name is already in the brand block. Its size follows the viewport (--gw-display-wordmark) and it never makes the
1417
1454
  page scroll sideways: a name too long for the line is cut. The mark is drawn in currentColor (an inline SVG); an
1418
- <img> mark keeps its own colours, so leave it out and keep the name. Not on phones, as on gemboss.ai. 24px more room
1419
- under it than gemboss.ai gives: the kit's footer is a painted band (white like the page), and the breathing gate
1420
- wants half the letters' size between them and its edge (67px under 123px letters at 1366px). */
1455
+ <img> mark keeps its own colours, so leave it out and keep the name. Values are gemboss.ai's, 1:1 (Chris 26/10/2026):
1456
+ weight 800, 48px above, nothing below but the footer's own 32px, the mark 7vw wide (40–96px), hidden up to 620px.
1457
+ The footer is not painted (transparent on the page), so the breathing gate reads it as part of the page. */
1421
1458
  .gw-footer__wordmark {
1422
1459
  display: flex;
1423
1460
  align-items: center;
1424
1461
  justify-content: center;
1425
1462
  gap: 24px;
1426
- padding-block: 48px 24px;
1463
+ padding-block: 48px 0;
1427
1464
  color: color-mix(in srgb, var(--gw-ink) 10%, transparent);
1465
+ font-family: var(--gw-font);
1428
1466
  font-size: var(--gw-display-wordmark);
1429
- font-weight: 700;
1467
+ font-weight: 800;
1430
1468
  letter-spacing: var(--gw-display-tracking);
1431
1469
  line-height: 1;
1432
1470
  white-space: nowrap;
1433
1471
  user-select: none;
1434
1472
  }
1435
1473
  .gw-footer__wordmark > svg,
1436
- .gw-footer__wordmark > img { flex: none; width: auto; height: 0.9em; }
1474
+ .gw-footer__wordmark > img { flex: none; width: clamp(40px, 7vw, 96px); height: auto; }
1437
1475
  .gw-footer__wordmark > span { min-width: 0; overflow-x: clip; }
1438
- @media (max-width: 767px) { .gw-footer__wordmark { gap: 16px; } }
1439
- @media (max-width: 479px) { .gw-footer__wordmark { display: none; } }
1476
+ @media (max-width: 640px) { .gw-footer__wordmark { gap: 16px; } }
1477
+ @media (max-width: 620px) { .gw-footer__wordmark { display: none; } }
1440
1478
 
1441
1479
  @media (max-width: 768px), (pointer: coarse) {
1442
1480
  .gw-footer__ask a { display: flex; align-items: center; min-height: var(--gw-tap-min); padding-block: 0; }
1443
1481
  }
1444
- @media (prefers-reduced-motion: reduce) {
1445
- .gw-footer__fold > summary::after { transition: none; }
1446
- }
1447
1482
  /* a forced-colours screen drops backgrounds: draw the chevron and the ↗ in its text colour */
1448
1483
  @media (forced-colors: active) {
1449
- .gw-footer__ext,
1450
- .gw-footer__fold > summary::after { forced-color-adjust: none; background: CanvasText; }
1484
+ .gw-footer__ext:empty { forced-color-adjust: none; background: CanvasText; }
1485
+ .gw-footer__fold > summary::after { border-color: CanvasText; }
1451
1486
  }
1452
1487
 
1453
1488
  /* sections: hero, logos, stats, features, split, showcase, steps, quote, pricing, CTA. */
@@ -2229,3 +2264,75 @@ a.gw-rating-badge:hover { border-color: var(--gw-line-strong); }
2229
2264
  .gw-rating-badge__body { display: grid; gap: 2px; min-width: 0; }
2230
2265
  .gw-rating-badge__score { display: flex; align-items: center; gap: 8px; color: var(--gw-ink); font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
2231
2266
  .gw-rating-badge__count { color: var(--gw-muted); }
2267
+
2268
+ /* effects: the spotlight (0.9.0, Chris 25/10/2026, issue #74), a named hover effect for the things you click.
2269
+ data-gw-spot on an <a href>, a <button>, or a card that holds exactly one of them (a plan card, the mega menu's
2270
+ feature card, the primary button of a night band, a .gw-card--link): a soft light in the brand's accent follows the
2271
+ pointer over it. Opt-in per element: a page without the attribute does not change.
2272
+ website.js wires the host (data-gw-ready="spot") and puts .gw-spot on the element that carries the light: the host,
2273
+ or the one its data-gw-spot="<selector>" names inside it. It writes the pointer's place there (--gw-spot-x,
2274
+ --gw-spot-y, trailing it a little every frame); this file draws the light on its ::before, under its words, in its
2275
+ own corners (border-radius: inherit, so the radius profile holds), and grows the light's radius on hover.
2276
+ - Pointer devices only: nothing here applies without (hover: hover) and (pointer: fine).
2277
+ - Keyboard focus (on the host, or on the one action of a card) shows the same light, centred.
2278
+ - Reduced motion: the light stands still, centred, and appears without a transition.
2279
+ - Words under the light: while a thing is lit, its muted words read --gw-ink-2 (the token, set as --gw-muted on the
2280
+ host), so they keep 4.5:1 at the light's middle; they turn back once the light has gone. A colour, no movement.
2281
+ - Without @property or color-mix(), website.js puts .gw-spot nowhere: no light at all, nothing half drawn.
2282
+ - One colour, the accent family, as the page has it (the -dark family in dark mode and in night bands). Never on a
2283
+ heading or running text: a heading stays ink, with no gradient and no accent words (check: spot-target).
2284
+ The radius is the one animated property that is neither transform nor opacity: it repaints one layer of one
2285
+ element. Registered, so it can transition; inherited, so the ::before (and an inner element) reads the host's. */
2286
+ @property --gw-spot-r {
2287
+ syntax: "<length>";
2288
+ inherits: true;
2289
+ initial-value: 0px;
2290
+ }
2291
+
2292
+ @media (hover: hover) and (pointer: fine) {
2293
+ /* the host: no light at rest (a lit card does not light a lit button inside it), the light's size on hover and focus */
2294
+ [data-gw-ready~="spot"] { --gw-spot-r: 0px; }
2295
+ [data-gw-ready~="spot"]:is(:hover, :focus-visible) { --gw-spot-r: var(--gw-spot-radius); --gw-muted: var(--gw-ink-2); }
2296
+ /* a card whose one action has the keyboard focus; a rule of its own, so a browser without :has() drops only it */
2297
+ [data-gw-ready~="spot"]:has(:focus-visible) { --gw-spot-r: var(--gw-spot-radius); --gw-muted: var(--gw-ink-2); }
2298
+ .gw-btn[data-gw-ready~="spot"]:is(:hover, :focus-visible) { --gw-spot-r: var(--gw-spot-radius-action); }
2299
+
2300
+ /* the element that carries the light: positioned for its layer, isolated so the layer sits under its words.
2301
+ :where(), so a site's own position for an inner element wins. */
2302
+ :where(.gw-spot) { position: relative; isolation: isolate; }
2303
+ /* the light. Not on a busy button: its ::before is the ring. */
2304
+ .gw-spot:not([aria-busy="true"])::before {
2305
+ --gw-spot-mix: var(--gw-spot-strength);
2306
+ content: "";
2307
+ position: absolute;
2308
+ inset: 0;
2309
+ z-index: -1;
2310
+ border-radius: inherit;
2311
+ background: radial-gradient(circle var(--gw-spot-r) at var(--gw-spot-x, 50%) var(--gw-spot-y, 50%), color-mix(in srgb, var(--gw-accent) var(--gw-spot-mix), transparent), transparent 70%);
2312
+ pointer-events: none;
2313
+ transition: --gw-spot-r var(--gw-spot-ms) var(--gw-spot-ease);
2314
+ }
2315
+ /* a button is small and filled: a stronger light. Its words keep their contrast: the light is the accent over the
2316
+ button's fill, so under the accent action profile it lies between the accent and the accent-strong, the two fills
2317
+ the build checks the words on. */
2318
+ .gw-btn.gw-spot:not([aria-busy="true"])::before { --gw-spot-mix: var(--gw-spot-strength-action); }
2319
+ /* the mega menu's feature card is the Stage, a night ground: a stronger light, or it would not show */
2320
+ .gw-mega__feature.gw-spot:not([aria-busy="true"])::before { --gw-spot-mix: var(--gw-spot-strength-stage); }
2321
+
2322
+ /* words under the light: lit, muted words are ink-2 at once (rules above); back to muted only once the light has gone
2323
+ out (a colour that waits the light's time, no movement). :where(), so a part's own transition wins. */
2324
+ :where([data-gw-ready~="spot"] *) { transition: color 0s linear var(--gw-spot-ms); }
2325
+ :where([data-gw-ready~="spot"]:is(:hover, :focus-visible) *) { transition-delay: 0s; }
2326
+ :where([data-gw-ready~="spot"]:has(:focus-visible) *) { transition-delay: 0s; }
2327
+
2328
+ /* keyboard focus: the light in the middle, so focus is never weaker than hover */
2329
+ :is([data-gw-ready~="spot"]:focus-visible, [data-gw-ready~="spot"]:focus-visible *).gw-spot::before { --gw-spot-x: 50%; --gw-spot-y: 50%; }
2330
+ :is([data-gw-ready~="spot"]:has(:focus-visible), [data-gw-ready~="spot"]:has(:focus-visible) *).gw-spot::before { --gw-spot-x: 50%; --gw-spot-y: 50%; }
2331
+ }
2332
+
2333
+ /* reduced motion: a still light in the middle, there at once and gone at once, the words with it (website.js follows
2334
+ no pointer then) */
2335
+ @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
2336
+ .gw-spot:not([aria-busy="true"])::before { --gw-spot-x: 50%; --gw-spot-y: 50%; transition: none; }
2337
+ :where([data-gw-ready~="spot"] *) { transition: none; }
2338
+ }