rastrillo / fiesta Public

rst-side: a sidebar inside a panel, a row of links when the panel is narrow

A settings screen, a help centre or the landing page's sample shows a
few sections of one thing beside the open one. The site had its own
sidebar for this, and it switched to a row of links at a window width
(560px): in a card on a desktop the column kept its place in a panel
with no room for it. rst-side answers to its own width through a
container query, so the same markup is a column of links beside the
content in a wide panel and a row above it in a narrow one, on any
screen. In the row each label stays on one line and the row scrolls
inside the nav, so the page never scrolls sideways.

The links are a menu's rows: muted at rest, the current link on the
accent wash in ink at 600 as a dropdown's current item, and the menu's
hover, press and tap tints and top highlight, so a page with a side nav
and a dropdown says "here" one way. They are addresses, declared in
the state's sampleURLs, so the reference markup works with no script
and no link points at nothing. The side joins the focus scope, since a
page may put it outside an rst-page; the side's links join the icon
rule, the target floor (with a width floor, for a short label in the
row), forced colours and print. In print the current link drops its
wash and is marked by its ink and weight, as the shell's current nav
item is. Both :where() lists are widened in place, each entry naming
exactly what it adds; every other rule positively requires rst-side or
rst-side-nav.

tokens.css had never held an @container block. On a scratch copy with
the block in, every CSS reader read it as it reads an @media block: an
orphan attribute spelling, a rule matching 1.0 markup, a value no
example writes, a shape token and an untracked control, each placed
inside the block, were all named. No reader needed a fix.

Tests, red by assertion against the example with no side CSS:
TestTheSideNavIsAColumnBesideItsPanel (links side by side, no border,
the content under the nav), TestANarrowPanelPutsTheLinksInARow (no
hairline; long labels on four rows, with nothing to scroll), and
TestTheSideNavLinksAreMenuRows (no wash, no tints, weight 400, 16px
links, the browser's 1px ring). TestTheSideNavLinksWorkWithNoScript
holds the markup and is already passing: red with the Members link
pointed at /settings/people (not a declared route), and with it taken
out of the tab order. The CSS alone was red in the declaration gate and
the new-rule gate (the two widened :where() lists) and the tap
inventory, until the widenings and the inventory row went in. Named
mutations, each red by name: the grid columns dropped, the border
written as border-right (the RTL leg), the container query disabled or
written as a window query (the 300px panel at 1280), overflow-x dropped
(the page 354px wide at 320), max-content dropped (a long label on five
lines), the hover, tap tint, weight, focus scope and floor each
dropped, the current link out of forced colours or print, and the
content rule written as div:not(...) (refused by the grammar: names
inside :not() never count).

Outside the plan's files: the sizing fixture carries a side nav (and
fixtureCounts two of its links), since the tap drive fails on an
inventory row it measures nothing for; the class-spelling fixture
carries a wide and a narrow side, so both spellings of the rules,
container block included, are compared; and leakUnchanged lets Spanish
keep General, the settings section's own word there.

The blurb, the label "Four sections, the first open" and the sample
words are the approved English, byte for byte. Their translations in
the eleven gallery catalogs, and the sample words General and "The
workspace's name, and the language it opens in.", are machine drafts
from that English, not reviewed; drafts.json names each. Settings,
Members, Billing and Notifications were already in the sample tables.
Heights measured by the drives (Desktop needs 190px; Mobile en and ar
273, ga and ru 297), with the table's 20px of slack.

Size, gzip level 6 (allotment 360, on target):
  fiesta.css        23,412 -> 23,699 (+287)
  fiesta-plain.css  22,698 -> 22,979 (+281)
  fiesta-signal.css 22,810 -> 23,091 (+281)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 5aea74db6f019602f37352da3fb9a445127f5f49 parent 3c9ea43
37 files changed, +626 −27
  • check/baseline_test.go +27 −10
  • check/depth_media_test.go +2 −2
  • check/side_browser_test.go +361 −0
  • check/sizing_browser_test.go +1 −0
  • check/sizing_test.go +1 −0
  • check/testdata/fixtures/sizing.html +1 −1
  • check/testdata/fixtures/spelling-class.html +2 −0
  • css/tokens.css +57 −4
  • examples/examples.json +22 −0
  • examples/prose.json +3 −0
  • examples/side-nav/four-sections-the-first-open.html +24 −0
  • internal/gallery/heights.go +4 −0
  • internal/gallery/i18n.go +12 −10
  • internal/gallery/i18n/ar.json +2 −0
  • internal/gallery/i18n/bn.json +2 −0
  • internal/gallery/i18n/drafts.json +64 −0
  • internal/gallery/i18n/es.json +2 −0
  • internal/gallery/i18n/ga.json +2 −0
  • internal/gallery/i18n/hi.json +2 −0
  • internal/gallery/i18n/ja.json +2 −0
  • internal/gallery/i18n/pt-PT.json +2 −0
  • internal/gallery/i18n/ru.json +2 −0
  • internal/gallery/i18n/samples.ar.json +2 −0
  • internal/gallery/i18n/samples.bn.json +2 −0
  • internal/gallery/i18n/samples.es.json +2 −0
  • internal/gallery/i18n/samples.ga.json +2 −0
  • internal/gallery/i18n/samples.hi.json +2 −0
  • internal/gallery/i18n/samples.ja.json +2 −0
  • internal/gallery/i18n/samples.pt-PT.json +2 −0
  • internal/gallery/i18n/samples.ru.json +2 −0
  • internal/gallery/i18n/samples.vi.json +2 −0
  • internal/gallery/i18n/samples.yue.json +2 −0
  • internal/gallery/i18n/samples.zh-Hans.json +2 −0
  • internal/gallery/i18n/vi.json +2 −0
  • internal/gallery/i18n/yue.json +2 −0
  • internal/gallery/i18n/zh-Hans.json +2 −0
  • internal/gallery/nav.go +1 −0

CI passed — run details

CI log
=== gofmt ===
=== no-rastrillo ===
go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$' -count=1 .
ok  	amadan.net/rastrillo/fiesta	0.215s
=== size ===
go test ./internal/build -run '^TestTheOneLinkIsUnder25KiBGzipped$' -count=1 -v
=== RUN   TestTheOneLinkIsUnder25KiBGzipped
    build_test.go:138: fiesta-plain.css: 157567 bytes, 22979 bytes gzipped (22.4 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta-signal.css: 157662 bytes, 23091 bytes gzipped (22.5 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta.css: 160481 bytes, 23699 bytes gzipped (23.1 KiB of the 25.0 KiB limit)
--- PASS: TestTheOneLinkIsUnder25KiBGzipped (0.02s)
PASS
ok  	amadan.net/rastrillo/fiesta/internal/build	0.025s
=== history ===
history: 27 commits; release tags: v1.0.0
=== suite ===
--- shard 0 of 48 ---
==> shard 0 of 48: make root
go build ./...
go vet ./...
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh root 'internal/gallery@3 cmd/fiesta@2 internal/www@2'
ok  	amadan.net/rastrillo/fiesta	2.412s
ok  	amadan.net/rastrillo/fiesta/check	6.663s
ok  	amadan.net/rastrillo/fiesta/example	0.180s
?   	amadan.net/rastrillo/fiesta/hack/seed/english	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/axe	0.094s
ok  	amadan.net/rastrillo/fiesta/internal/build	1.044s
ok  	amadan.net/rastrillo/fiesta/internal/codeview	0.014s
?   	amadan.net/rastrillo/fiesta/internal/harness	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/markup	0.012s
ok  	amadan.net/rastrillo/fiesta/internal/nodetest	0.390s
ok  	amadan.net/rastrillo/fiesta/internal/site	2.087s
ok  	amadan.net/rastrillo/fiesta/internal/tokens	0.007s
suite: root took 23s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m11.590000s 0m1.980000s
--- shard 1 of 48 ---
==> shard 1 of 48: make test.internal.gallery~1of3
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.gallery~1of3'
gotest: slice 1 of 3 of ./internal/gallery/ runs 36 of 108 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	64.213s
suite: test.internal.gallery~1of3 took 79s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m50.430000s 0m1.230000s
--- shard 2 of 48 ---
==> shard 2 of 48: make test.internal.gallery~2of3
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.gallery~2of3'
gotest: slice 2 of 3 of ./internal/gallery/ runs 36 of 108 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	66.245s
suite: test.internal.gallery~2of3 took 81s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m53.220000s 0m1.350000s
--- shard 3 of 48 ---
==> shard 3 of 48: make test.internal.gallery~3of3
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.gallery~3of3'
gotest: slice 3 of 3 of ./internal/gallery/ runs 36 of 108 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	62.414s
suite: test.internal.gallery~3of3 took 76s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m48.410000s 0m1.340000s
--- shard 4 of 48 ---
==> shard 4 of 48: make test.cmd.fiesta~1of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'cmd.fiesta~1of2'
gotest: slice 1 of 2 of ./cmd/fiesta/ runs 7 of 12 test tests
ok  	amadan.net/rastrillo/fiesta/cmd/fiesta	45.872s
suite: test.cmd.fiesta~1of2 took 60s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m32.810000s 0m2.090000s
--- shard 5 of 48 ---
==> shard 5 of 48: make test.cmd.fiesta~2of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'cmd.fiesta~2of2'
gotest: slice 2 of 2 of ./cmd/fiesta/ runs 5 of 12 test tests
ok  	amadan.net/rastrillo/fiesta/cmd/fiesta	49.482s
suite: test.cmd.fiesta~2of2 took 63s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m35.670000s 0m2.350000s
--- shard 6 of 48 ---
==> shard 6 of 48: make test.internal.www~1of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.www~1of2'
gotest: slice 1 of 2 of ./internal/www/ runs 11 of 25 test tests
ok  	amadan.net/rastrillo/fiesta/internal/www	61.251s
suite: test.internal.www~1of2 took 76s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m47.430000s 0m1.610000s
--- shard 7 of 48 ---
==> shard 7 of 48: make test.internal.www~2of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.www~2of2'
gotest: slice 2 of 2 of ./internal/www/ runs 14 of 25 test tests
ok  	amadan.net/rastrillo/fiesta/internal/www	59.593s
suite: test.internal.www~2of2 took 74s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m45.140000s 0m1.700000s
--- shard 8 of 48 ---
==> shard 8 of 48: make staticcheck
go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 -tags browser ./...
suite: staticcheck took 17s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m13.650000s 0m0.920000s
--- shard 9 of 48 ---
==> shard 9 of 48: make govulncheck
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 2 vulnerabilities in packages you import and 5
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
suite: govulncheck took 14s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m2.960000s 0m0.350000s
--- shard 10 of 48 ---
==> shard 10 of 48: make gitleaks
go run github.com/zricethezav/gitleaks/v8@v8.30.1 git --no-banner --redact --log-opts='--full-history --diff-filter=tuxdb HEAD' .
6:44AM INF Unknown SCM platform. Use --platform to include links in findings. host=amadan.net
6:44AM INF 27 commits scanned.
6:44AM INF scanned ~8377586 bytes (8.38 MB) in 15.1s
6:44AM INF no leaks found
suite: gitleaks took 17s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m6.980000s 0m0.230000s
--- shard 11 of 48 ---
==> shard 11 of 48: make browser.internal.harness
./hack/gotest.sh browser 'internal.harness'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: ./internal/harness/ runs its 10 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/harness	4.644s
suite: browser.internal.harness took 19s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m1.650000s 0m1.300000s
--- shard 12 of 48 ---
==> shard 12 of 48: make browser.check~1of13
./hack/gotest.sh browser 'check~1of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 1 of 13 of ./check/ runs 16 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	88.685s
suite: browser.check~1of13 took 106s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m48.030000s 0m14.120000s
--- shard 13 of 48 ---
==> shard 13 of 48: make browser.check~2of13
./hack/gotest.sh browser 'check~2of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 2 of 13 of ./check/ runs 16 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	92.262s
suite: browser.check~2of13 took 110s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m49.880000s 0m13.260000s
--- shard 14 of 48 ---
==> shard 14 of 48: make browser.check~3of13
./hack/gotest.sh browser 'check~3of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 3 of 13 of ./check/ runs 16 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	89.922s
suite: browser.check~3of13 took 107s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m40.010000s 0m11.130000s
--- shard 15 of 48 ---
==> shard 15 of 48: make browser.check~4of13
./hack/gotest.sh browser 'check~4of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 4 of 13 of ./check/ runs 16 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	87.441s
suite: browser.check~4of13 took 105s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m52.430000s 0m15.600000s
--- shard 16 of 48 ---
==> shard 16 of 48: make browser.check~5of13
./hack/gotest.sh browser 'check~5of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 5 of 13 of ./check/ runs 18 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	81.943s
suite: browser.check~5of13 took 99s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m38.220000s 0m13.840000s
--- shard 17 of 48 ---
==> shard 17 of 48: make browser.check~6of13
./hack/gotest.sh browser 'check~6of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 6 of 13 of ./check/ runs 18 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	76.475s
suite: browser.check~6of13 took 94s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m43.490000s 0m14.420000s
--- shard 18 of 48 ---
==> shard 18 of 48: make browser.check~7of13
./hack/gotest.sh browser 'check~7of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 7 of 13 of ./check/ runs 18 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	82.588s
suite: browser.check~7of13 took 100s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m44.090000s 0m14.260000s
--- shard 19 of 48 ---
==> shard 19 of 48: make browser.check~8of13
./hack/gotest.sh browser 'check~8of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 8 of 13 of ./check/ runs 18 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	89.306s
suite: browser.check~8of13 took 107s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m46.950000s 0m16.160000s
--- shard 20 of 48 ---
==> shard 20 of 48: make browser.check~9of13
./hack/gotest.sh browser 'check~9of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 9 of 13 of ./check/ runs 18 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	75.252s
suite: browser.check~9of13 took 93s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m29.570000s 0m9.260000s
--- shard 21 of 48 ---
==> shard 21 of 48: make browser.check~10of13
./hack/gotest.sh browser 'check~10of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 10 of 13 of ./check/ runs 18 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	81.334s
suite: browser.check~10of13 took 99s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m38.610000s 0m12.650000s
--- shard 22 of 48 ---
==> shard 22 of 48: make browser.check~11of13
./hack/gotest.sh browser 'check~11of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 11 of 13 of ./check/ runs 18 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	83.358s
suite: browser.check~11of13 took 101s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m42.460000s 0m14.390000s
--- shard 23 of 48 ---
==> shard 23 of 48: make browser.check~12of13
./hack/gotest.sh browser 'check~12of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 12 of 13 of ./check/ runs 17 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	83.406s
suite: browser.check~12of13 took 101s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m45.140000s 0m15.390000s
--- shard 24 of 48 ---
==> shard 24 of 48: make browser.check~13of13
./hack/gotest.sh browser 'check~13of13'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 13 of 13 of ./check/ runs 17 of 224 browser tests
ok  	amadan.net/rastrillo/fiesta/check	80.196s
suite: browser.check~13of13 took 97s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m36.720000s 0m10.550000s
--- shard 25 of 48 ---
==> shard 25 of 48: make browser.internal.gallery~1of18
./hack/gotest.sh browser 'internal.gallery~1of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	38.466s
suite: browser.internal.gallery~1of18 took 55s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m15.870000s 0m3.820000s
--- shard 26 of 48 ---
==> shard 26 of 48: make browser.internal.gallery~2of18
./hack/gotest.sh browser 'internal.gallery~2of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	48.525s
suite: browser.internal.gallery~2of18 took 66s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m13.620000s 0m2.610000s
--- shard 27 of 48 ---
==> shard 27 of 48: make browser.internal.gallery~3of18
./hack/gotest.sh browser 'internal.gallery~3of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 3 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	58.648s
suite: browser.internal.gallery~3of18 took 76s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m16.120000s 0m4.190000s
--- shard 28 of 48 ---
==> shard 28 of 48: make browser.internal.gallery~4of18
./hack/gotest.sh browser 'internal.gallery~4of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 4 of 18 of ./internal/gallery/ runs 6 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	50.337s
suite: browser.internal.gallery~4of18 took 67s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m14.240000s 0m2.910000s
--- shard 29 of 48 ---
==> shard 29 of 48: make browser.internal.gallery~5of18
./hack/gotest.sh browser 'internal.gallery~5of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 5 of 18 of ./internal/gallery/ runs 6 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	61.625s
suite: browser.internal.gallery~5of18 took 79s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m18.940000s 0m5.400000s
--- shard 30 of 48 ---
==> shard 30 of 48: make browser.internal.gallery~6of18
./hack/gotest.sh browser 'internal.gallery~6of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 6 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	56.292s
suite: browser.internal.gallery~6of18 took 73s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m16.050000s 0m4.040000s
--- shard 31 of 48 ---
==> shard 31 of 48: make browser.internal.gallery~7of18
./hack/gotest.sh browser 'internal.gallery~7of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 7 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	57.403s
suite: browser.internal.gallery~7of18 took 74s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m14.030000s 0m2.800000s
--- shard 32 of 48 ---
==> shard 32 of 48: make browser.internal.gallery~8of18
./hack/gotest.sh browser 'internal.gallery~8of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 8 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	58.358s
suite: browser.internal.gallery~8of18 took 76s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m17.870000s 0m4.740000s
--- shard 33 of 48 ---
==> shard 33 of 48: make browser.internal.gallery~9of18
./hack/gotest.sh browser 'internal.gallery~9of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 9 of 18 of ./internal/gallery/ runs 6 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	65.453s
suite: browser.internal.gallery~9of18 took 83s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m22.760000s 0m7.380000s
--- shard 34 of 48 ---
==> shard 34 of 48: make browser.internal.gallery~10of18
./hack/gotest.sh browser 'internal.gallery~10of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 10 of 18 of ./internal/gallery/ runs 2 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	63.203s
suite: browser.internal.gallery~10of18 took 80s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m14.020000s 0m2.840000s
--- shard 35 of 48 ---
==> shard 35 of 48: make browser.internal.gallery~11of18
./hack/gotest.sh browser 'internal.gallery~11of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 11 of 18 of ./internal/gallery/ runs 2 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	65.296s
suite: browser.internal.gallery~11of18 took 82s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m14.390000s 0m2.930000s
--- shard 36 of 48 ---
==> shard 36 of 48: make browser.internal.gallery~12of18
./hack/gotest.sh browser 'internal.gallery~12of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 12 of 18 of ./internal/gallery/ runs 2 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	65.079s
suite: browser.internal.gallery~12of18 took 82s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.000000s 0m5.040000s
--- shard 37 of 48 ---
==> shard 37 of 48: make browser.internal.gallery~13of18
./hack/gotest.sh browser 'internal.gallery~13of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 13 of 18 of ./internal/gallery/ runs 4 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	64.876s
suite: browser.internal.gallery~13of18 took 82s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m18.980000s 0m5.200000s
--- shard 38 of 48 ---
==> shard 38 of 48: make browser.internal.gallery~14of18
./hack/gotest.sh browser 'internal.gallery~14of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 14 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	68.489s
suite: browser.internal.gallery~14of18 took 86s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.550000s 0m5.430000s
--- shard 39 of 48 ---
==> shard 39 of 48: make browser.internal.gallery~15of18
./hack/gotest.sh browser 'internal.gallery~15of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 15 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	67.622s
suite: browser.internal.gallery~15of18 took 85s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.610000s 0m5.560000s
--- shard 40 of 48 ---
==> shard 40 of 48: make browser.internal.gallery~16of18
./hack/gotest.sh browser 'internal.gallery~16of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 16 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	64.487s
suite: browser.internal.gallery~16of18 took 81s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m16.630000s 0m4.570000s
--- shard 41 of 48 ---
==> shard 41 of 48: make browser.internal.gallery~17of18
./hack/gotest.sh browser 'internal.gallery~17of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 17 of 18 of ./internal/gallery/ runs 5 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	67.311s
suite: browser.internal.gallery~17of18 took 84s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.240000s 0m5.670000s
--- shard 42 of 48 ---
==> shard 42 of 48: make browser.internal.gallery~18of18
./hack/gotest.sh browser 'internal.gallery~18of18'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 18 of 18 of ./internal/gallery/ runs 6 of 84 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	68.401s
suite: browser.internal.gallery~18of18 took 85s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m20.610000s 0m6.380000s
--- shard 43 of 48 ---
==> shard 43 of 48: make browser.internal.gallery.history
./hack/gotest.sh browser 'internal.gallery.history'
gotest: on ip-172-31-24-192, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: ./internal/gallery/history/ runs its 3 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery/history	32.941s
suite: browser.internal.gallery.history took 48s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m21.710000s 0m1.990000s
--- shard 44 of 48 ---
==> shard 44 of 48: make browser.internal.www~1of4
./hack/gotest.sh browser 'internal.www~1of4'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 4 of ./internal/www/ runs 4 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	69.796s
suite: browser.internal.www~1of4 took 86s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m31.650000s 0m2.250000s
--- shard 45 of 48 ---
==> shard 45 of 48: make browser.internal.www~2of4
./hack/gotest.sh browser 'internal.www~2of4'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 4 of ./internal/www/ runs 6 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	67.482s
suite: browser.internal.www~2of4 took 84s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m46.310000s 0m2.680000s
--- shard 46 of 48 ---
==> shard 46 of 48: make browser.internal.www~3of4
./hack/gotest.sh browser 'internal.www~3of4'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 3 of 4 of ./internal/www/ runs 6 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	80.950s
suite: browser.internal.www~3of4 took 97s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m52.610000s 0m3.010000s
--- shard 47 of 48 ---
==> shard 47 of 48: make browser.internal.www~4of4
./hack/gotest.sh browser 'internal.www~4of4'
gotest: on ip-172-31-24-192, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 4 of 4 of ./internal/www/ runs 6 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	76.922s
suite: browser.internal.www~4of4 took 93s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m46.510000s 0m3.000000s
diff --git a/check/baseline_test.go b/check/baseline_test.go
index 0209ed3..7cc661e 100644
--- a/check/baseline_test.go
+++ b/check/baseline_test.go
@@ -62,24 +62,41 @@ type widening struct {
// the rule already styles changes weight.
const footFlat = "the footer row's menus are flat, as a shell tail's are"
-// A seg-tabs button is a control as a seg-tabs link is, so the rule
-// that sizes an icon inside a control names it too. :where() weighs
-// nothing, so no icon 1.0 already sizes changes.
-const segTabButtonIcons = "a seg-tabs button is a control, so an icon in it is sized as one"
+// A seg-tabs button is a control as a seg-tabs link is, and a side
+// nav link is a menu row, so the rule that sizes an icon inside a
+// control names both. :where() weighs nothing, so no icon 1.0 already
+// sizes changes.
+const newControlIcons = "a seg-tabs button and a side nav link are controls, so an icon in one is sized as in a control"
+
+// A side nav may sit outside an rst-page, as the landing page's sample
+// does, so the focus scope names it, or its links would show the
+// browser's thin ring there. :where() weighs nothing, so no focus ring
+// 1.0 already draws changes.
+const sideFocus = "a side nav outside an rst-page still shows the focus ring"
// widenedSelectors is keyed by the 1.0 selector. Each entry must be
// used: an entry for a selector the CSS no longer widens is a promise
// about nothing.
var widenedSelectors = map[string]widening{
":where(.rst-btn, .rst-search, .rst-search__clear, .rst-ftok a, .rst-help, .rst-dropdown > summary, .rst-dropdown__menu a, .rst-dropdown__menu button, .rst-dropdown__menu .rst-menu-group > summary, .rst-row-menu__panel a, .rst-row-menu__panel button, .rst-locale button, .rst-scheme button, .rst-combo__option, .rst-dtp__row, .rst-dtp__pick, .rst-cal__nav, .rst-row__action, .rst-row-menu > summary, .rst-pagination > a, .rst-seg-tabs a, .rst-switch, .rst-choice__cards label, .rst-tblock__head, .rst-bulkbar__close, .rst-bulkbar__escalate, .rst-modal-close, .rst-modal-panel > nav a, .rst-back-nav a, .rst-signin__providers > a, .rst-shell__brand, .rst-shell__nav a, .rst-shell__menu > summary, .rst-shell__chrome > summary, .rst-shell__account > summary, .rst-shell__back > a) .icon": {
- Now: ":where(.rst-btn, .rst-search, .rst-search__clear, .rst-ftok a, .rst-help, .rst-dropdown > summary, .rst-dropdown__menu a, .rst-dropdown__menu button, .rst-dropdown__menu .rst-menu-group > summary, .rst-row-menu__panel a, .rst-row-menu__panel button, .rst-locale button, .rst-scheme button, .rst-combo__option, .rst-dtp__row, .rst-dtp__pick, .rst-cal__nav, .rst-row__action, .rst-row-menu > summary, .rst-pagination > a, .rst-seg-tabs a, .rst-seg-tabs > button, .rst-switch, .rst-choice__cards label, .rst-tblock__head, .rst-bulkbar__close, .rst-bulkbar__escalate, .rst-modal-close, .rst-modal-panel > nav a, .rst-back-nav a, .rst-signin__providers > a, .rst-shell__brand, .rst-shell__nav a, .rst-shell__menu > summary, .rst-shell__chrome > summary, .rst-shell__account > summary, .rst-shell__back > a) .icon",
- Added: []string{".rst-seg-tabs > button"},
- Why: segTabButtonIcons,
+ Now: ":where(.rst-btn, .rst-search, .rst-search__clear, .rst-ftok a, .rst-help, .rst-dropdown > summary, .rst-dropdown__menu a, .rst-dropdown__menu button, .rst-dropdown__menu .rst-menu-group > summary, .rst-row-menu__panel a, .rst-row-menu__panel button, .rst-locale button, .rst-scheme button, .rst-combo__option, .rst-dtp__row, .rst-dtp__pick, .rst-cal__nav, .rst-row__action, .rst-row-menu > summary, .rst-pagination > a, .rst-seg-tabs a, .rst-seg-tabs > button, .rst-side__nav a, .rst-switch, .rst-choice__cards label, .rst-tblock__head, .rst-bulkbar__close, .rst-bulkbar__escalate, .rst-modal-close, .rst-modal-panel > nav a, .rst-back-nav a, .rst-signin__providers > a, .rst-shell__brand, .rst-shell__nav a, .rst-shell__menu > summary, .rst-shell__chrome > summary, .rst-shell__account > summary, .rst-shell__back > a) .icon",
+ Added: []string{".rst-seg-tabs > button", ".rst-side__nav a"},
+ Why: newControlIcons,
},
":where([rst-btn], [rst-search], [rst-search-clear], [rst-ftok] a, [rst-help], [rst-dropdown] > summary, [rst-dropdown-menu] a, [rst-dropdown-menu] button, [rst-dropdown-menu] [rst-menu-group] > summary, [rst-row-menu-panel] a, [rst-row-menu-panel] button, [rst-locale] button, [rst-scheme] button, [rst-combo-option], [rst-dtp-row], [rst-dtp-pick], [rst-cal-nav], [rst-row-action], [rst-row-menu] > summary, [rst-pagination] > a, [rst-seg-tabs] a, [rst-switch], [rst-choice-cards] label, [rst-tblock-head], [rst-bulkbar-close], [rst-bulkbar-escalate], [rst-modal-close], [rst-modal-panel] > nav a, [rst-back-nav] a, [rst-signin-providers] > a, [rst-shell-brand], [rst-shell-nav] a, [rst-shell-menu] > summary, [rst-shell-chrome] > summary, [rst-shell-account] > summary, [rst-shell-back] > a) .icon": {
- Now: ":where([rst-btn], [rst-search], [rst-search-clear], [rst-ftok] a, [rst-help], [rst-dropdown] > summary, [rst-dropdown-menu] a, [rst-dropdown-menu] button, [rst-dropdown-menu] [rst-menu-group] > summary, [rst-row-menu-panel] a, [rst-row-menu-panel] button, [rst-locale] button, [rst-scheme] button, [rst-combo-option], [rst-dtp-row], [rst-dtp-pick], [rst-cal-nav], [rst-row-action], [rst-row-menu] > summary, [rst-pagination] > a, [rst-seg-tabs] a, [rst-seg-tabs] > button, [rst-switch], [rst-choice-cards] label, [rst-tblock-head], [rst-bulkbar-close], [rst-bulkbar-escalate], [rst-modal-close], [rst-modal-panel] > nav a, [rst-back-nav] a, [rst-signin-providers] > a, [rst-shell-brand], [rst-shell-nav] a, [rst-shell-menu] > summary, [rst-shell-chrome] > summary, [rst-shell-account] > summary, [rst-shell-back] > a) .icon",
- Added: []string{"[rst-seg-tabs] > button"},
- Why: segTabButtonIcons,
+ Now: ":where([rst-btn], [rst-search], [rst-search-clear], [rst-ftok] a, [rst-help], [rst-dropdown] > summary, [rst-dropdown-menu] a, [rst-dropdown-menu] button, [rst-dropdown-menu] [rst-menu-group] > summary, [rst-row-menu-panel] a, [rst-row-menu-panel] button, [rst-locale] button, [rst-scheme] button, [rst-combo-option], [rst-dtp-row], [rst-dtp-pick], [rst-cal-nav], [rst-row-action], [rst-row-menu] > summary, [rst-pagination] > a, [rst-seg-tabs] a, [rst-seg-tabs] > button, [rst-side-nav] a, [rst-switch], [rst-choice-cards] label, [rst-tblock-head], [rst-bulkbar-close], [rst-bulkbar-escalate], [rst-modal-close], [rst-modal-panel] > nav a, [rst-back-nav] a, [rst-signin-providers] > a, [rst-shell-brand], [rst-shell-nav] a, [rst-shell-menu] > summary, [rst-shell-chrome] > summary, [rst-shell-account] > summary, [rst-shell-back] > a) .icon",
+ Added: []string{"[rst-seg-tabs] > button", "[rst-side-nav] a"},
+ Why: newControlIcons,
+ },
+ ":where(.rst-page, .rst-page-header, .rst-list, .rst-lbar, .rst-search, .rst-empty, .rst-pagination) :focus-visible": {
+ Now: ":where(.rst-page, .rst-page-header, .rst-list, .rst-lbar, .rst-search, .rst-empty, .rst-pagination, .rst-side) :focus-visible",
+ Added: []string{".rst-side"},
+ Why: sideFocus,
+ },
+ ":where([rst-page], [rst-page-header], [rst-list], [rst-lbar], [rst-search], [rst-empty], [rst-pagination]) :focus-visible": {
+ Now: ":where([rst-page], [rst-page-header], [rst-list], [rst-lbar], [rst-search], [rst-empty], [rst-pagination], [rst-side]) :focus-visible",
+ Added: []string{"[rst-side]"},
+ Why: sideFocus,
},
".rst-dropdown:not(.rst-shell__account, .rst-shell__profile, .rst-shell__tail .rst-dropdown, .rst-bulkbar .rst-dropdown, .rst-dropdown__menu .rst-dropdown) > summary:not(.rst-btn)": {
Now: ".rst-dropdown:not(.rst-shell__account, .rst-shell__profile, .rst-shell__tail .rst-dropdown, .rst-page__foot .rst-dropdown, .rst-bulkbar .rst-dropdown, .rst-dropdown__menu .rst-dropdown) > summary:not(.rst-btn)",
diff --git a/check/depth_media_test.go b/check/depth_media_test.go
index 5762cf4..a613c5d 100644
--- a/check/depth_media_test.go
+++ b/check/depth_media_test.go
@@ -30,11 +30,11 @@ func TestForcedColoursAndPrintFlattenTheAxisAndCoverEveryComponent(t *testing.T)
want []string
}{
{"@media (forced-colors: active) {", append(flat, "html:root {", "[rst-btn]", "[rst-box]", "[rst-card]", "[rst-stats]", "[rst-list]", "[rst-input]", "[rst-textarea]", "[rst-search]", "[rst-dropdown]", "[rst-switch-track]", "[rst-switch-track]::after",
- "[rst-shell-sidebar] [rst-shell-nav] a[aria-current]", "[rst-shell-console] [rst-shell-nav] a[aria-current]", "[rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a[aria-current]", "[rst-dropdown-menu] a[aria-current]", "[rst-pagination] [aria-current=\"page\"]", "[rst-seg-tabs] a[aria-current]", `[rst-seg-tabs] > button[aria-pressed="true"]`,
+ "[rst-shell-sidebar] [rst-shell-nav] a[aria-current]", "[rst-shell-console] [rst-shell-nav] a[aria-current]", "[rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a[aria-current]", "[rst-dropdown-menu] a[aria-current]", "[rst-pagination] [aria-current=\"page\"]", "[rst-seg-tabs] a[aria-current]", `[rst-seg-tabs] > button[aria-pressed="true"]`, "[rst-side-nav] a[aria-current]",
`[rst-shell-sidebar][rst-shell-sidebar~="index"] [rst-shell-nav] > a[aria-current]:hover`, `[rst-shell-console][rst-shell-console~="index"] [rst-shell-nav] > a[aria-current]:active`,
"ButtonBorder", "CanvasText", "ButtonText", "Highlight", "HighlightText", "box-shadow: none")},
{"@media print {", append(flat, "html:root {", "--rst-accent-fill: transparent", "--rst-accent-fill-hover: transparent", "--rst-on-accent-fill: var(--rst-text)", "--rst-danger-fill: transparent", "--rst-on-danger-fill-hover: var(--rst-text)", "--rst-danger-hover-filter: none", "--rst-control-face-hover: transparent", "--rst-control-text-hover: var(--rst-text)", "--rst-knob: var(--rst-text)",
- "[rst-btn]", "[rst-box]", "[rst-card]", "[rst-stats]", "[rst-list]", "[rst-input]", "[rst-textarea]", "[rst-search]", "[rst-dropdown]", "[rst-switch-track]", "[rst-switch-track]::after", "[rst-switch]:has(input:checked) [rst-switch-track]", "[rst-tblock] > [rst-tblock-head]:has(> input:checked) > [rst-switch-track]", "[rst-seg-tabs] a[aria-current]", `[rst-seg-tabs] > button[aria-pressed="true"]`, "[rst-pagination] [aria-current=\"page\"]", "[rst-dropdown-menu]", "[rst-row-menu-panel]",
+ "[rst-btn]", "[rst-box]", "[rst-card]", "[rst-stats]", "[rst-list]", "[rst-input]", "[rst-textarea]", "[rst-search]", "[rst-dropdown]", "[rst-switch-track]", "[rst-switch-track]::after", "[rst-switch]:has(input:checked) [rst-switch-track]", "[rst-tblock] > [rst-tblock-head]:has(> input:checked) > [rst-switch-track]", "[rst-seg-tabs] a[aria-current]", `[rst-seg-tabs] > button[aria-pressed="true"]`, "[rst-side-nav] a[aria-current]", "[rst-pagination] [aria-current=\"page\"]", "[rst-dropdown-menu]", "[rst-row-menu-panel]",
"[rst-shell-sidebar] [rst-shell-nav] a[aria-current]", "[rst-shell-console] [rst-shell-nav] a[aria-current]", "var(--rst-line-strong)", "background-image: none", "box-shadow: none", "outline: 1px solid var(--rst-line-strong)")},
} {
i := strings.LastIndex(css, block.header)
diff --git a/check/side_browser_test.go b/check/side_browser_test.go
new file mode 100644
index 0000000..54a4a9e
--- /dev/null
+++ b/check/side_browser_test.go
@@ -0,0 +1,361 @@
+//go:build browser
+
+// The side nav as a reader meets it: a column of links beside the
+// panel's content where the panel is wide, and a row of links above it
+// where the panel is narrow, whatever the window's width, since a
+// settings card on a desktop is as narrow as a phone; links that go
+// somewhere with no script; and links that look and answer like the
+// rows of a menu, focus ring included, on a page with no rst-page.
+package check
+
+import (
+ "context"
+ "fmt"
+ "maps"
+ "math"
+ "slices"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/chromedp/cdproto/emulation"
+ "github.com/chromedp/chromedp"
+ "github.com/chromedp/chromedp/kb"
+
+ "amadan.net/rastrillo/fiesta"
+ "amadan.net/rastrillo/fiesta/example"
+ "amadan.net/rastrillo/fiesta/internal/harness"
+)
+
+// sideNavExample is the side-nav example's one state (its id and the
+// routes its links point at) and that state as a document, its assets
+// flattened beside the page as the sizing mux serves them.
+func sideNavExample(t *testing.T) (example.State, string) {
+ t.Helper()
+ entry, ok := manifest(t).Entry("side-nav")
+ if !ok || len(entry.States) != 1 {
+ t.Fatal("examples.json has no side-nav entry with one state")
+ }
+ state := entry.States[0]
+ doc, err := example.Document(fiesta.FS(), "side-nav", state.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return state, parityLinks(doc)
+}
+
+// inPanel is doc with its body's markup inside a panel of the given
+// width, the way a page puts the side nav in a card.
+func inPanel(t *testing.T, doc, width string) string {
+ t.Helper()
+ if !strings.Contains(doc, "<body>\n") || !strings.Contains(doc, "\n</body>") {
+ t.Fatal("the side-nav document has no <body> lines to wrap in a panel")
+ }
+ doc = strings.Replace(doc, "<body>\n", `<body>`+"\n"+`<div id="panel" style="inline-size: `+width+`">`+"\n", 1)
+ return strings.Replace(doc, "\n</body>", "\n</div>\n</body>", 1)
+}
+
+// longLabels is doc with each link's label made long, so four of them
+// cannot fit a narrow panel side by side.
+func longLabels(t *testing.T, doc string) string {
+ t.Helper()
+ for _, l := range []string{"General", "Members", "Billing", "Notifications"} {
+ was := ">" + l + "</a>"
+ if !strings.Contains(doc, was) {
+ t.Fatalf("the side-nav document has no link labelled %s", l)
+ }
+ doc = strings.Replace(doc, was, ">"+l+" for the whole workspace</a>", 1)
+ }
+ return doc
+}
+
+// sideGeometry is what the layout tests read: the panel's content box
+// (the body's, for the page with no panel), the nav's box with its
+// scroll and client widths and its four border widths, the content's
+// box, each link's box and how many lines its label takes, and the
+// document's scroll and client widths.
+type sideGeometry struct {
+ Panel, Nav, Content sideBox
+ Links []sideBox
+ Lines []int
+ NavScroll, NavClient float64
+ Start, End, Top, Bottom string
+ DocScroll, DocClient float64
+}
+
+type sideBox struct{ Left, Right, Top, Bottom float64 }
+
+const sideGeometryJS = `(() => {
+ const side = document.querySelector("[rst-side]"), nav = side.querySelector("[rst-side-nav]"),
+ content = side.querySelector(":scope > :not([rst-side-nav])"), ns = getComputedStyle(nav);
+ const b = (el) => { const r = el.getBoundingClientRect(); return {Left: r.left, Right: r.right, Top: r.top, Bottom: r.bottom}; };
+ const p = side.parentElement, pr = p.getBoundingClientRect(), ps = getComputedStyle(p), px = (v) => parseFloat(v) || 0;
+ const rtl = getComputedStyle(side).direction === "rtl";
+ return JSON.stringify({
+ Panel: {Left: pr.left + px(ps.borderLeftWidth) + px(ps.paddingLeft), Right: pr.right - px(ps.borderRightWidth) - px(ps.paddingRight), Top: pr.top, Bottom: pr.bottom},
+ Nav: b(nav), Content: b(content), Links: [...nav.querySelectorAll("a")].map(b),
+ Lines: [...nav.querySelectorAll("a")].map((a) => { const r = document.createRange(); r.selectNodeContents(a); return new Set([...r.getClientRects()].map((q) => Math.round(q.top))).size; }),
+ NavScroll: nav.scrollWidth, NavClient: nav.clientWidth,
+ Start: rtl ? ns.borderRightWidth : ns.borderLeftWidth, End: rtl ? ns.borderLeftWidth : ns.borderRightWidth,
+ Top: ns.borderTopWidth, Bottom: ns.borderBottomWidth,
+ DocScroll: document.documentElement.scrollWidth, DocClient: document.documentElement.clientWidth});
+})()`
+
+// readSide opens path at the given viewport width and reads it.
+func readSide(t *testing.T, ctx context.Context, origin, path string, width int64) sideGeometry {
+ t.Helper()
+ mustRun(t, ctx, chromedp.EmulateViewport(width, 800), chromedp.Navigate(origin+path), chromedp.WaitReady("[rst-side-nav]", chromedp.ByQuery))
+ settleUntil(t, ctx, `document.readyState === "complete"`)
+ var g sideGeometry
+ at(t, ctx, sideGeometryJS, &g)
+ if len(g.Links) != 4 {
+ t.Fatalf("%s: the nav holds %d links; the example has four", path, len(g.Links))
+ }
+ return g
+}
+
+// sameEdge is whether a and b are the same edge, to half a pixel.
+func sameEdge(a, b float64) bool { return math.Abs(a-b) <= 0.5 }
+
+// TestTheSideNavIsAColumnBesideItsPanel: in a 640px panel the links
+// stack in a column with one shared edge, the content sits beside the
+// column, and a 1px hairline at the column's inline end divides them;
+// right to left, all of it mirrors.
+func TestTheSideNavIsAColumnBesideItsPanel(t *testing.T) {
+ _, doc := sideNavExample(t)
+ wide := inPanel(t, doc, "640px")
+ rig := sizingRig(t, false, map[string]string{"/": wide, "/rtl": inArabic(t, wide)})
+ ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
+ defer cancel()
+ for _, path := range []string{"/", "/rtl"} {
+ g := readSide(t, ctx, rig.Origin, path, 1280)
+ rtl := path == "/rtl"
+ // The link's edge at the column's inline start: the left in
+ // English, the right in Arabic.
+ start := func(b sideBox) float64 {
+ if rtl {
+ return b.Right
+ }
+ return b.Left
+ }
+ for i, l := range g.Links {
+ if !sameEdge(start(l), start(g.Links[0])) {
+ t.Errorf("%s: link %d starts at %.1f, link 1 at %.1f; the links of a column share one edge", path, i+1, start(l), start(g.Links[0]))
+ }
+ if i > 0 && l.Top < g.Links[i-1].Bottom-0.5 {
+ t.Errorf("%s: link %d's top is %.1f, above link %d's bottom %.1f; the links stack", path, i+1, l.Top, i, g.Links[i-1].Bottom)
+ }
+ }
+ if !rtl && g.Content.Left < g.Nav.Right-0.5 {
+ t.Errorf("%s: the content's left edge is %.1f, left of the nav's right edge %.1f; the content sits beside the nav", path, g.Content.Left, g.Nav.Right)
+ }
+ if rtl && g.Content.Right > g.Nav.Left+0.5 {
+ t.Errorf("%s: the content's right edge is %.1f, right of the nav's left edge %.1f; right to left, the content sits to the nav's left", path, g.Content.Right, g.Nav.Left)
+ }
+ if g.End != "1px" || g.Start != "0px" || g.Bottom != "0px" {
+ t.Errorf("%s: the nav's borders are %s at its inline end, %s at its start and %s below; want one 1px hairline at the inline end", path, g.End, g.Start, g.Bottom)
+ }
+ if !sameEdge(g.Nav.Top, g.Content.Top) {
+ t.Errorf("%s: the nav's top is %.1f and the content's %.1f; side by side, they start on one line", path, g.Nav.Top, g.Content.Top)
+ }
+ }
+}
+
+// TestANarrowPanelPutsTheLinksInARow: in a 300px panel on a wide
+// screen, and on a 320px phone, the nav spans the panel above the
+// content, its links one row, each label on one line, with the
+// hairline under the row; with
+// labels too long for the width, the row scrolls inside the nav while
+// the page itself never scrolls sideways.
+func TestANarrowPanelPutsTheLinksInARow(t *testing.T) {
+ _, doc := sideNavExample(t)
+ narrow, long := inPanel(t, doc, "300px"), inPanel(t, longLabels(t, doc), "300px")
+ pages := map[string]string{
+ "/panel": narrow, "/panel-rtl": inArabic(t, narrow),
+ "/panel-long": long, "/panel-long-rtl": inArabic(t, long),
+ "/page": doc, "/page-rtl": inArabic(t, doc),
+ "/page-long": longLabels(t, doc), "/page-long-rtl": inArabic(t, longLabels(t, doc)),
+ }
+ rig := sizingRig(t, false, pages)
+ ctx, cancel := context.WithTimeout(rig.Context(), 90*time.Second)
+ defer cancel()
+ for _, path := range slices.Sorted(maps.Keys(pages)) {
+ width := int64(1280)
+ if strings.HasPrefix(path, "/page") {
+ width = 320
+ }
+ where := fmt.Sprintf("%s at %d", path, width)
+ g := readSide(t, ctx, rig.Origin, path, width)
+ if !sameEdge(g.Nav.Left, g.Panel.Left) || !sameEdge(g.Nav.Right, g.Panel.Right) {
+ t.Errorf("%s: the nav spans %.1f to %.1f in a panel of %.1f to %.1f; in a narrow panel it spans the panel", where, g.Nav.Left, g.Nav.Right, g.Panel.Left, g.Panel.Right)
+ }
+ if g.Content.Top < g.Nav.Bottom-0.5 {
+ t.Errorf("%s: the content's top is %.1f, above the nav's bottom %.1f; in a narrow panel the nav sits above the content", where, g.Content.Top, g.Nav.Bottom)
+ }
+ for i, l := range g.Links {
+ if !sameEdge(l.Top, g.Links[0].Top) {
+ t.Errorf("%s: link %d's top is %.1f, link 1's %.1f; in a narrow panel the links are one row", where, i+1, l.Top, g.Links[0].Top)
+ }
+ if g.Lines[i] != 1 {
+ t.Errorf("%s: link %d's label takes %d lines; in the row a label stays on one line and the row scrolls", where, i+1, g.Lines[i])
+ }
+ }
+ if g.Bottom != "1px" || g.End != "0px" || g.Start != "0px" {
+ t.Errorf("%s: the nav's borders are %s below, %s at its inline end and %s at its start; want the hairline under the nav and none beside it", where, g.Bottom, g.End, g.Start)
+ }
+ if strings.Contains(path, "-long") && g.NavScroll <= g.NavClient {
+ t.Errorf("%s: the nav's row is %.0fpx wide in %.0fpx; four long labels must overflow it, or this leg proves nothing about scrolling", where, g.NavScroll, g.NavClient)
+ }
+ if g.DocScroll > g.DocClient {
+ t.Errorf("%s: the page is %.0fpx wide in a %.0fpx viewport; the row scrolls inside the nav, never the page", where, g.DocScroll, g.DocClient)
+ }
+ }
+}
+
+// arrivedPage is what each of the example's routes answers: a page
+// that a drive can tell it arrived at, and whose own script would
+// rewrite it, so the drive can also tell that scripts were off.
+const arrivedPage = `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>arrived</title></head><body><p id="arrived">arrived</p>` +
+ `<script>document.getElementById("arrived").textContent = "scripted";</script></body></html>`
+
+// TestTheSideNavLinksWorkWithNoScript: with scripts off, each link is
+// reached with Tab and followed with Enter to the route it names, and
+// every route is one the state declares, so the reference markup has no
+// link that goes nowhere and needs no script to switch sections.
+func TestTheSideNavLinksWorkWithNoScript(t *testing.T) {
+ state, doc := sideNavExample(t)
+ pages := map[string]string{"/": doc}
+ for _, u := range state.SampleURLs {
+ pages[u] = arrivedPage
+ }
+ rig := sizingRig(t, false, pages)
+ ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
+ defer cancel()
+ mustRun(t, ctx, emulation.SetScriptExecutionDisabled(true))
+ var hrefs []string
+ mustRun(t, ctx, chromedp.Navigate(rig.Origin+"/"), chromedp.WaitReady("[rst-side-nav]", chromedp.ByQuery))
+ at(t, ctx, `JSON.stringify([...document.querySelectorAll("[rst-side-nav] a")].map(a => a.getAttribute("href")))`, &hrefs)
+ if len(hrefs) != 4 {
+ t.Fatalf("the nav holds %d links; the example has four", len(hrefs))
+ }
+ for _, h := range hrefs {
+ if !slices.Contains(state.SampleURLs, h) {
+ t.Fatalf("the link to %q is not one of the state's sampleURLs %v; a link must go somewhere the app has", h, state.SampleURLs)
+ }
+ }
+ for i, h := range hrefs {
+ mustRun(t, ctx, chromedp.Navigate(rig.Origin+"/"), chromedp.WaitReady("[rst-side-nav]", chromedp.ByQuery))
+ for range i + 1 {
+ mustRun(t, ctx, chromedp.KeyEvent(kb.Tab))
+ }
+ var focused string
+ at(t, ctx, `JSON.stringify(document.activeElement.getAttribute("href") || document.activeElement.tagName)`, &focused)
+ if focused != h {
+ t.Errorf("after %d presses of Tab the focus is on %q, want the link to %q", i+1, focused, h)
+ continue
+ }
+ var path, said string
+ mustRun(t, ctx, chromedp.KeyEvent(kb.Enter), chromedp.WaitReady("#arrived", chromedp.ByQuery))
+ at(t, ctx, `JSON.stringify(location.pathname)`, &path)
+ at(t, ctx, `JSON.stringify(document.getElementById("arrived").textContent)`, &said)
+ if said != "arrived" {
+ t.Fatalf("the route's own script ran (%q); this leg is not the page a reader with no script gets", said)
+ }
+ if path != h {
+ t.Errorf("Enter on the link to %q arrived at %q", h, path)
+ }
+ }
+}
+
+// menuRowsPage is the example beside a dropdown's menu, open, with a
+// current item and a plain one, in Day's light scheme, so each link's
+// states are read against a menu row's under the same stylesheets.
+func menuRowsPage(t *testing.T, doc string) string {
+ t.Helper()
+ const menu = `<details rst-dropdown open><summary>Menu</summary><div rst-dropdown-menu>` +
+ `<a id="menu-current" href="#one" aria-current="page">One</a><a id="menu-plain" href="#two">Two</a></div></details>`
+ doc = strings.Replace(doc, `<html lang="en" dir="ltr">`, `<html lang="en" dir="ltr" data-theme="light">`, 1)
+ return strings.Replace(doc, "\n</body>", "\n"+menu+"\n</body>", 1)
+}
+
+// TestTheSideNavLinksAreMenuRows: in Day, light, with a mouse, the
+// current link wears the menu's current row, a hovered link the menu's
+// hovered row with its top highlight, a pressed one the press tint and
+// a tapped one the tap tint; every link is at least the target floor
+// tall; and a link focused from the keyboard shows the 2px ring though
+// no rst-page is around it.
+func TestTheSideNavLinksAreMenuRows(t *testing.T) {
+ _, doc := sideNavExample(t)
+ rig := sizingRig(t, false, map[string]string{"/": menuRowsPage(t, doc)}, harness.WithHover())
+ ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
+ defer cancel()
+ mustRun(t, ctx, chromedp.EmulateViewport(1280, 800), chromedp.Navigate(rig.Origin+"/"), chromedp.WaitReady("[rst-side-nav]", chromedp.ByQuery))
+ var hover bool
+ at(t, ctx, `JSON.stringify(matchMedia("(hover: hover)").matches)`, &hover)
+ if !hover {
+ t.Fatal("(hover: hover) is false on this rig; the hover rules cannot be read here")
+ }
+ still(t, ctx)
+ const current, plain = `[rst-side-nav] a[aria-current]`, `[rst-side-nav] a[href="/settings/members"]`
+ face := func(sel string, states ...string) (bg, fg, shadow string) {
+ forceState(t, ctx, sel, states...)
+ bg, fg, shadow = computed(t, ctx, sel, "background-color"), computed(t, ctx, sel, "color"), computed(t, ctx, sel, "box-shadow")
+ forceState(t, ctx, sel)
+ return bg, fg, shadow
+ }
+ for _, c := range []struct {
+ what, link, item string
+ states []string
+ }{
+ {"current", current, "#menu-current", nil},
+ {"hovered", plain, "#menu-plain", []string{"hover"}},
+ {"pressed", plain, "#menu-plain", []string{"hover", "active"}},
+ {"tapped", plain, "#menu-plain", []string{"active"}},
+ } {
+ lb, lf, ls := face(c.link, c.states...)
+ mb, mf, ms := face(c.item, c.states...)
+ if lb != mb || ls != ms {
+ t.Errorf("the %s link is %s with box-shadow %q; the %s menu row is %s with %q", c.what, lb, ls, c.what, mb, ms)
+ }
+ if c.what != "tapped" && lf != mf {
+ t.Errorf("the %s link's text is %s; the %s menu row's is %s", c.what, lf, c.what, mf)
+ }
+ }
+ if rest, _, _ := face(plain); rest == computed(t, ctx, "#menu-current", "background-color") {
+ t.Errorf("a link at rest is %s, the current row's wash; only the current link is on it", rest)
+ }
+ if w := computed(t, ctx, current, "font-weight"); w != "600" {
+ t.Errorf("the current link's weight is %s, want 600, as a current menu row's label", w)
+ }
+ var floor struct {
+ Target float64
+ Heights []float64
+ }
+ at(t, ctx, `(() => { const p = document.createElement("div"); p.style.blockSize = "var(--rst-target)"; document.body.append(p);
+ const target = p.getBoundingClientRect().height; p.remove();
+ return JSON.stringify({Target: target, Heights: [...document.querySelectorAll("[rst-side-nav] a")].map(a => a.getBoundingClientRect().height)}); })()`, &floor)
+ if floor.Target < 30 {
+ t.Fatalf("--rst-target reads %.1fpx; the probe is broken, not the links", floor.Target)
+ }
+ for i, h := range floor.Heights {
+ if h < floor.Target-0.5 {
+ t.Errorf("link %d is %.1fpx tall, under the %.1fpx target floor", i+1, h, floor.Target)
+ }
+ }
+ mustRun(t, ctx, chromedp.KeyEvent(kb.Tab))
+ var ring struct {
+ Href, Style, Width string
+ InPage bool
+ }
+ at(t, ctx, `(() => { const a = document.activeElement, s = getComputedStyle(a);
+ return JSON.stringify({Href: a.getAttribute("href") || a.tagName, Style: s.outlineStyle, Width: s.outlineWidth, InPage: !!a.closest("[rst-page], .rst-page")}); })()`, &ring)
+ if ring.Href != "/settings/general" {
+ t.Fatalf("the first Tab focused %q, not the first link", ring.Href)
+ }
+ if ring.InPage {
+ t.Fatal("the focused link is inside an rst-page; this leg is for a side nav outside one")
+ }
+ if ring.Style != "solid" || ring.Width != "2px" {
+ t.Errorf("a link focused from the keyboard has outline %s %s, want solid 2px though no rst-page is around it", ring.Style, ring.Width)
+ }
+}
diff --git a/check/sizing_browser_test.go b/check/sizing_browser_test.go
index 366383e..b0fc454 100644
--- a/check/sizing_browser_test.go
+++ b/check/sizing_browser_test.go
@@ -710,6 +710,7 @@ var fixtureCounts = []struct {
{`[data-partial="bulk-bar"] [rst-dropdown-menu] button`, 2},
{`[data-partial="seg-tabs"] a`, 2},
{`[data-partial="seg-tabs"] [rst-seg-tabs] > button`, 2},
+ {`[data-partial="side-nav"] [rst-side-nav] a`, 2},
{`[data-partial="dropdown"] [rst-dropdown-menu] a`, 1},
{`[data-extra="short-labels"] [rst-bulkbar-escalate]`, 1},
{`[data-extra="short-labels"] a[rst-person]`, 1},
diff --git a/check/sizing_test.go b/check/sizing_test.go
index b0055b0..9263bc3 100644
--- a/check/sizing_test.go
+++ b/check/sizing_test.go
@@ -345,6 +345,7 @@ var tapInventory = []tapEntry{
{"[rst-pagination] > a", "Pagination chips", "rst-pagination", "[rst-pagination] > a"},
{"[rst-seg-tabs] a", "Segmented tabs", "rst-seg-tabs", "[rst-seg-tabs] a"},
{"[rst-seg-tabs] > button", "Segmented tabs: buttons", "rst-seg-tabs", "[rst-seg-tabs] > button"},
+ {"[rst-side-nav] a", "Side nav links", "rst-side-nav", "[rst-side-nav] a"},
{"[rst-switch]", "Switch", "rst-switch", "[rst-switch] input"},
{"[rst-choice-cards] label", "Choice cards", "rst-choice-cards", "[rst-choice-cards] input"},
{"[rst-tblock-head]", "Toggle-block head", "rst-tblock-head", "[rst-tblock-head] input"},
diff --git a/check/testdata/fixtures/sizing.html b/check/testdata/fixtures/sizing.html
index 39fe594..1fc9305 100644
--- a/check/testdata/fixtures/sizing.html
+++ b/check/testdata/fixtures/sizing.html
@@ -168,7 +168,7 @@
</details></div><div rst-page data-partial="form-foot"><div rst-form-foot>
<button rst-btn="primary lg" type="submit">Save</button>
<a rst-btn="lg" href="/admin/posts">Back to posts</a>
-</div></div><div rst-page data-partial="field-check"><label rst-switch><input type="checkbox" name="notify" checked><span rst-switch-track aria-hidden="true"></span> Email me about replies</label></div><div rst-page data-partial="choice-field"><fieldset rst-choice><legend>Plan</legend><div rst-choice-cards><label><input type="radio" name="plan" value="free"><span><span rst-choice-title>Free</span><span rst-choice-desc>Good to start.</span></span></label><label><input type="radio" name="plan" value="pro" checked><span><span rst-choice-title>Pro</span><span rst-choice-desc>For growing teams.</span></span></label></div></fieldset></div><div rst-page data-partial="seg-tabs"><nav rst-seg-tabs aria-label="Sections"><a href="?tab=basics" aria-current="page">Basics</a><a href="?tab=advanced">Advanced</a></nav><div rst-seg-tabs role="group" aria-label="View"><button type="button" aria-pressed="true">List</button><button type="button" aria-pressed="false">Grid</button></div></div><div rst-page data-partial="confirm-form"><form rst-form-actions method="post" action="/orders/1/refund"><input type="hidden" name="csrf" value="tok"><input type="hidden" name="id" value="42"><a rst-btn="ghost lg" href="/orders/1">Cancel</a><button type="submit" rst-btn="danger lg">Refund €10.00</button></form></div><div rst-page data-partial="back-nav"><nav rst-back-nav><a href="/orders/1">← Order AB3PX</a></nav></div><div rst-page data-partial="bulk-bar"><div rst-bulkbar><a rst-bulkbar-close href="/orders" aria-label="Done selecting"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></a><span rst-bulkbar-selection><span rst-bulkbar-count>3 selected</span><a rst-bulkbar-escalate href="/orders?select=all">Select all 412 matching</a></span><details rst-dropdown name="rst-menus"><summary>Actions<span rst-caret aria-hidden="true"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></span></summary><div rst-dropdown-menu><button type="submit" name="action" value="export">Export</button><button type="submit" name="action" value="refund" class="rst-danger">Refund…</button></div></details></div></div><div rst-page data-partial="media-grid"><div rst-media>
+</div></div><div rst-page data-partial="field-check"><label rst-switch><input type="checkbox" name="notify" checked><span rst-switch-track aria-hidden="true"></span> Email me about replies</label></div><div rst-page data-partial="choice-field"><fieldset rst-choice><legend>Plan</legend><div rst-choice-cards><label><input type="radio" name="plan" value="free"><span><span rst-choice-title>Free</span><span rst-choice-desc>Good to start.</span></span></label><label><input type="radio" name="plan" value="pro" checked><span><span rst-choice-title>Pro</span><span rst-choice-desc>For growing teams.</span></span></label></div></fieldset></div><div rst-page data-partial="seg-tabs"><nav rst-seg-tabs aria-label="Sections"><a href="?tab=basics" aria-current="page">Basics</a><a href="?tab=advanced">Advanced</a></nav><div rst-seg-tabs role="group" aria-label="View"><button type="button" aria-pressed="true">List</button><button type="button" aria-pressed="false">Grid</button></div></div><div rst-page data-partial="side-nav"><div rst-side><nav rst-side-nav aria-label="Settings"><a href="/settings/general" aria-current="page">General</a><a href="/settings/members">Members</a></nav><div><p>The workspace's name, and the language it opens in.</p></div></div></div><div rst-page data-partial="confirm-form"><form rst-form-actions method="post" action="/orders/1/refund"><input type="hidden" name="csrf" value="tok"><input type="hidden" name="id" value="42"><a rst-btn="ghost lg" href="/orders/1">Cancel</a><button type="submit" rst-btn="danger lg">Refund €10.00</button></form></div><div rst-page data-partial="back-nav"><nav rst-back-nav><a href="/orders/1">← Order AB3PX</a></nav></div><div rst-page data-partial="bulk-bar"><div rst-bulkbar><a rst-bulkbar-close href="/orders" aria-label="Done selecting"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></a><span rst-bulkbar-selection><span rst-bulkbar-count>3 selected</span><a rst-bulkbar-escalate href="/orders?select=all">Select all 412 matching</a></span><details rst-dropdown name="rst-menus"><summary>Actions<span rst-caret aria-hidden="true"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></span></summary><div rst-dropdown-menu><button type="submit" name="action" value="export">Export</button><button type="submit" name="action" value="refund" class="rst-danger">Refund…</button></div></details></div></div><div rst-page data-partial="media-grid"><div rst-media>
<div rst-media-tile>
<div rst-media-frame>
<img src="/images/4471.png" alt="" loading="lazy" decoding="async" width="1280" height="3200">
diff --git a/check/testdata/fixtures/spelling-class.html b/check/testdata/fixtures/spelling-class.html
index 3468158..5d08112 100644
--- a/check/testdata/fixtures/spelling-class.html
+++ b/check/testdata/fixtures/spelling-class.html
@@ -68,6 +68,8 @@
<dl class="rst-detail" ><dt>Reference</dt><dd class="rst-mono" >AB-1001</dd></dl>
<div class="rst-snippet" ><code dir="ltr" translate="no">&lt;link rel="stylesheet" href="https://fiesta.rastrillo.org/1/fiesta.css"&gt;</code><button class="rst-btn rst-btn--sm rst-snippet__copy" type="button">Copy</button><span class="rst-snippet__status" role="status"></span></div>
<div class="rst-snippet" ><code dir="ltr" translate="no">fiesta.css</code><button class="rst-btn rst-btn--sm rst-snippet__copy" type="button">Copy</button><span class="rst-snippet__status" role="status">Copied</span></div>
+ <div class="rst-side" ><nav class="rst-side__nav" aria-label="Settings"><a href="#a" aria-current="page">General</a><a href="#a">Members</a></nav><div><h2>General</h2></div></div>
+ <div style="inline-size: 300px"><div class="rst-side" ><nav class="rst-side__nav" aria-label="Settings"><a href="#a" aria-current="page">General</a><a href="#a">Members</a></nav><div><h2>General</h2></div></div></div>
<form class="rst-form" method="post" action="#a">
<div class="rst-form-flow" >
diff --git a/css/tokens.css b/css/tokens.css
index c0d1958..72c5c01 100644
--- a/css/tokens.css
+++ b/css/tokens.css
@@ -235,15 +235,18 @@ body {
component that sizes its icon (the 12px caret, .rst-caret) still
wins. Not the list grid's title: it is the row's name, text that an
icon should follow, and the row is its target. */
-:where(.rst-btn, .rst-search, .rst-search__clear, .rst-ftok a, .rst-help, .rst-dropdown > summary, .rst-dropdown__menu a, .rst-dropdown__menu button, .rst-dropdown__menu .rst-menu-group > summary, .rst-row-menu__panel a, .rst-row-menu__panel button, .rst-locale button, .rst-scheme button, .rst-combo__option, .rst-dtp__row, .rst-dtp__pick, .rst-cal__nav, .rst-row__action, .rst-row-menu > summary, .rst-pagination > a, .rst-seg-tabs a, .rst-seg-tabs > button, .rst-switch, .rst-choice__cards label, .rst-tblock__head, .rst-bulkbar__close, .rst-bulkbar__escalate, .rst-modal-close, .rst-modal-panel > nav a, .rst-back-nav a, .rst-signin__providers > a, .rst-shell__brand, .rst-shell__nav a, .rst-shell__menu > summary, .rst-shell__chrome > summary, .rst-shell__account > summary, .rst-shell__back > a) .icon, :where([rst-btn], [rst-search], [rst-search-clear], [rst-ftok] a, [rst-help], [rst-dropdown] > summary, [rst-dropdown-menu] a, [rst-dropdown-menu] button, [rst-dropdown-menu] [rst-menu-group] > summary, [rst-row-menu-panel] a, [rst-row-menu-panel] button, [rst-locale] button, [rst-scheme] button, [rst-combo-option], [rst-dtp-row], [rst-dtp-pick], [rst-cal-nav], [rst-row-action], [rst-row-menu] > summary, [rst-pagination] > a, [rst-seg-tabs] a, [rst-seg-tabs] > button, [rst-switch], [rst-choice-cards] label, [rst-tblock-head], [rst-bulkbar-close], [rst-bulkbar-escalate], [rst-modal-close], [rst-modal-panel] > nav a, [rst-back-nav] a, [rst-signin-providers] > a, [rst-shell-brand], [rst-shell-nav] a, [rst-shell-menu] > summary, [rst-shell-chrome] > summary, [rst-shell-account] > summary, [rst-shell-back] > a) .icon {
+:where(.rst-btn, .rst-search, .rst-search__clear, .rst-ftok a, .rst-help, .rst-dropdown > summary, .rst-dropdown__menu a, .rst-dropdown__menu button, .rst-dropdown__menu .rst-menu-group > summary, .rst-row-menu__panel a, .rst-row-menu__panel button, .rst-locale button, .rst-scheme button, .rst-combo__option, .rst-dtp__row, .rst-dtp__pick, .rst-cal__nav, .rst-row__action, .rst-row-menu > summary, .rst-pagination > a, .rst-seg-tabs a, .rst-seg-tabs > button, .rst-side__nav a, .rst-switch, .rst-choice__cards label, .rst-tblock__head, .rst-bulkbar__close, .rst-bulkbar__escalate, .rst-modal-close, .rst-modal-panel > nav a, .rst-back-nav a, .rst-signin__providers > a, .rst-shell__brand, .rst-shell__nav a, .rst-shell__menu > summary, .rst-shell__chrome > summary, .rst-shell__account > summary, .rst-shell__back > a) .icon, :where([rst-btn], [rst-search], [rst-search-clear], [rst-ftok] a, [rst-help], [rst-dropdown] > summary, [rst-dropdown-menu] a, [rst-dropdown-menu] button, [rst-dropdown-menu] [rst-menu-group] > summary, [rst-row-menu-panel] a, [rst-row-menu-panel] button, [rst-locale] button, [rst-scheme] button, [rst-combo-option], [rst-dtp-row], [rst-dtp-pick], [rst-cal-nav], [rst-row-action], [rst-row-menu] > summary, [rst-pagination] > a, [rst-seg-tabs] a, [rst-seg-tabs] > button, [rst-side-nav] a, [rst-switch], [rst-choice-cards] label, [rst-tblock-head], [rst-bulkbar-close], [rst-bulkbar-escalate], [rst-modal-close], [rst-modal-panel] > nav a, [rst-back-nav] a, [rst-signin-providers] > a, [rst-shell-brand], [rst-shell-nav] a, [rst-shell-menu] > summary, [rst-shell-chrome] > summary, [rst-shell-account] > summary, [rst-shell-back] > a) .icon {
block-size: 1rem;
inline-size: 1rem;
}
/* Focus is visible everywhere inside the app column (.rst-page) and
inside any component rendered outside one. :where() contributes no
- specificity, so any rule below can override it. */
-:where(.rst-page, .rst-page-header, .rst-list, .rst-lbar, .rst-search, .rst-empty, .rst-pagination) :focus-visible, :where([rst-page], [rst-page-header], [rst-list], [rst-lbar], [rst-search], [rst-empty], [rst-pagination]) :focus-visible {
+ specificity, so any rule below can override it. The side nav is
+ here because a page may put one outside an rst-page, as the
+ landing page's sample does, and its links would show the browser's
+ thin ring there. */
+:where(.rst-page, .rst-page-header, .rst-list, .rst-lbar, .rst-search, .rst-empty, .rst-pagination, .rst-side) :focus-visible, :where([rst-page], [rst-page-header], [rst-list], [rst-lbar], [rst-search], [rst-empty], [rst-pagination], [rst-side]) :focus-visible {
outline: 2px solid var(--rst-accent);
outline-offset: 2px;
}
@@ -2176,6 +2179,48 @@ small.rst-field__hint, small[rst-field-hint] { display: block; font-size: var(--
of a tab that would answer a click. */
.rst-seg-tabs > button:disabled, [rst-seg-tabs] > button:disabled { color: var(--rst-text-faint); cursor: default; }
+/* side: a sidebar inside a panel, a column of links beside the
+ panel's content, the open section's. It answers to its own width,
+ not the window's, through a container query: a settings card on a
+ desktop is as narrow as a phone, and a window query would put a
+ column of links in a card with no room for it. The container is
+ rst-side itself, so the side must be given its width by its parent
+ (a block, a grid cell); in a flex row or an inline-block, which size
+ to their content, an inline-size container is zero wide.
+ fit-content(12rem) lets the column be as wide as its longest label,
+ up to 12rem, where a label wraps; minmax(0, 1fr) lets the content
+ shrink below its longest word instead of pushing the side wider than
+ its panel. */
+.rst-side, [rst-side] { container-type: inline-size; display: grid; grid-template-columns: fit-content(12rem) minmax(0, 1fr); }
+.rst-side__nav, [rst-side-nav] { align-content: start; border-inline-end: 1px solid var(--rst-line); display: grid; gap: 2px; padding: var(--rst-sp-3) var(--rst-sp-2); }
+.rst-side > :not(.rst-side__nav), [rst-side] > :not([rst-side-nav]) { padding: var(--rst-sp-4); }
+/* The links are a menu's rows, so a page with a side nav and a
+ dropdown has one way of saying "here" and one of saying "you can
+ go here": the current row on the accent wash in ink at 600, as the
+ dropdown's current item, and the hover, press and tap tints and the
+ top highlight of its other items. Muted at rest, so the current
+ link is the one that stands out; a hovered link comes up to ink, as
+ a menu row reads. The current link has no hover or press of its
+ own, as a menu's current item has none: it is where you are. */
+.rst-side__nav a, [rst-side-nav] a { border-radius: var(--rst-radius-sm); color: var(--rst-text-muted); font-size: var(--rst-fs-sm); padding: 0.4rem 0.65rem; text-decoration: none; -webkit-tap-highlight-color: transparent; }
+.rst-side__nav a[aria-current], [rst-side-nav] a[aria-current] { background: var(--rst-accent-soft); color: var(--rst-text); font-weight: 600; }
+.rst-side__nav a:not([aria-current]):active, [rst-side-nav] a:not([aria-current]):active { background: var(--rst-tint-tap, transparent); }
+@media (hover: hover) {
+ .rst-side__nav a:not([aria-current]):hover, [rst-side-nav] a:not([aria-current]):hover { background: var(--rst-tint-hover, var(--rst-accent-soft)); box-shadow: inset 0 1px 0 var(--rst-depth-hl, transparent); color: var(--rst-text); }
+ .rst-side__nav a:not([aria-current]):hover:active, [rst-side-nav] a:not([aria-current]):hover:active { background: var(--rst-tint-press, var(--rst-accent-soft)); }
+}
+/* A narrow side: both children span the grid, so the nav sits above
+ the content; its links are one row, each as wide as its label
+ (max-content, so a long label never wraps into a tall cell), and
+ the row scrolls inside the nav when it is wider than the panel, so
+ the page never scrolls sideways. The hairline moves under the nav.
+ 34rem is the width at which the page's own rows give up their
+ columns. */
+@container (max-width: 34rem) {
+ .rst-side__nav, [rst-side-nav], .rst-side > :not(.rst-side__nav), [rst-side] > :not([rst-side-nav]) { grid-column: 1 / -1; }
+ .rst-side__nav, [rst-side-nav] { border-block-end: 1px solid var(--rst-line); border-inline-end: 0; grid-auto-columns: max-content; grid-auto-flow: column; overflow-x: auto; }
+}
+
/* form layout — the interview rhythm and the save bar. */
.rst-form-flow > .rst-field + .rst-field, [rst-form-flow] > [rst-field] + [rst-field] { margin-top: var(--rst-sp-5); }
/* field-row aligns by the CONTROL row, not by the bottom of the box.
@@ -3560,7 +3605,7 @@ dialog.rst-modal-panel, dialog[rst-modal-panel] { block-size: auto; color: inher
.rst-switch__track::after, [rst-switch-track]::after { background: ButtonText; }
.rst-switch:has(input:checked) .rst-switch__track, [rst-switch]:has(input:checked) [rst-switch-track], .rst-tblock > .rst-tblock__head:has(> input:checked) > .rst-switch__track, [rst-tblock] > [rst-tblock-head]:has(> input:checked) > [rst-switch-track] { background: Highlight; }
.rst-switch:has(input:checked) .rst-switch__track::after, [rst-switch]:has(input:checked) [rst-switch-track]::after, .rst-tblock > .rst-tblock__head:has(> input:checked) > .rst-switch__track::after, [rst-tblock] > [rst-tblock-head]:has(> input:checked) > [rst-switch-track]::after { background: HighlightText; }
- .rst-shell-sidebar .rst-shell__nav a[aria-current], [rst-shell-sidebar] [rst-shell-nav] a[aria-current], .rst-shell-console .rst-shell__nav a[aria-current], [rst-shell-console] [rst-shell-nav] a[aria-current], .rst-shell-topbar .rst-shell__tail > .rst-shell__nav a[aria-current], [rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a[aria-current], .rst-dropdown__menu a[aria-current], [rst-dropdown-menu] a[aria-current], .rst-pagination [aria-current="page"], [rst-pagination] [aria-current="page"], .rst-seg-tabs a[aria-current], [rst-seg-tabs] a[aria-current], .rst-seg-tabs > button[aria-pressed="true"], [rst-seg-tabs] > button[aria-pressed="true"] { background: Highlight; box-shadow: none; color: HighlightText; }
+ .rst-shell-sidebar .rst-shell__nav a[aria-current], [rst-shell-sidebar] [rst-shell-nav] a[aria-current], .rst-shell-console .rst-shell__nav a[aria-current], [rst-shell-console] [rst-shell-nav] a[aria-current], .rst-shell-topbar .rst-shell__tail > .rst-shell__nav a[aria-current], [rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a[aria-current], .rst-dropdown__menu a[aria-current], [rst-dropdown-menu] a[aria-current], .rst-pagination [aria-current="page"], [rst-pagination] [aria-current="page"], .rst-seg-tabs a[aria-current], [rst-seg-tabs] a[aria-current], .rst-seg-tabs > button[aria-pressed="true"], [rst-seg-tabs] > button[aria-pressed="true"], .rst-side__nav a[aria-current], [rst-side-nav] a[aria-current] { background: Highlight; box-shadow: none; color: HighlightText; }
.rst-shell-sidebar.rst-shell-sidebar--index .rst-shell__nav > a[aria-current]:hover, .rst-shell-sidebar.rst-shell-sidebar--index .rst-shell__nav > a[aria-current]:active, [rst-shell-sidebar][rst-shell-sidebar~="index"] [rst-shell-nav] > a[aria-current]:hover, [rst-shell-sidebar][rst-shell-sidebar~="index"] [rst-shell-nav] > a[aria-current]:active, .rst-shell-console.rst-shell-console--index .rst-shell__nav > a[aria-current]:hover, .rst-shell-console.rst-shell-console--index .rst-shell__nav > a[aria-current]:active, [rst-shell-console][rst-shell-console~="index"] [rst-shell-nav] > a[aria-current]:hover, [rst-shell-console][rst-shell-console~="index"] [rst-shell-nav] > a[aria-current]:active { background: Highlight; color: HighlightText; }
}
/* Print drops every shadow, gradient, sheen and tint the same way,
@@ -3606,6 +3651,11 @@ dialog.rst-modal-panel, dialog[rst-modal-panel] { block-size: auto; color: inher
other chips keep their line: without this the current page is the
one chip with no outline on paper. */
.rst-pagination [aria-current="page"], [rst-pagination] [aria-current="page"] { border-color: var(--rst-line-strong); }
+ /* The current side link's wash is a background, which a printer
+ drops or keeps by the reader's settings, so on paper it is marked
+ by its ink and weight alone, every time, as the shell's current
+ nav item is. */
+ .rst-side__nav a[aria-current], [rst-side-nav] a[aria-current] { background: transparent; }
}
/* The browser's own tap flash is a different grey from the press tint,
@@ -3704,6 +3754,9 @@ a.rst-person, a[rst-person] { box-sizing: border-box; min-block-size: var(--rst-
touch query sets the phone's. */
.rst-pagination > a, [rst-pagination] > a, .rst-pagination > span, [rst-pagination] > span { align-content: center; box-sizing: border-box; min-block-size: var(--rst-target); }
.rst-seg-tabs a, [rst-seg-tabs] a, .rst-seg-tabs > button, [rst-seg-tabs] > button { align-content: center; box-sizing: border-box; min-block-size: var(--rst-target); min-inline-size: var(--rst-target); }
+/* A side nav link is a menu row, centred as one; in the narrow row it
+ is as wide as its label, so a short one gets the width floor too. */
+.rst-side__nav a, [rst-side-nav] a { align-content: center; box-sizing: border-box; min-block-size: var(--rst-target); min-inline-size: var(--rst-target); }
.rst-switch, [rst-switch], .rst-choice__cards label, [rst-choice-cards] label, .rst-tblock > .rst-tblock__head, [rst-tblock] > [rst-tblock-head] { box-sizing: border-box; min-block-size: var(--rst-target); }
/* flex: none because the bar is a flex row that, at 390, shrank the
diff --git a/examples/examples.json b/examples/examples.json
index 8f8bf98..99e859f 100644
--- a/examples/examples.json
+++ b/examples/examples.json
@@ -192,6 +192,28 @@
}
]
},
+ {
+ "name": "side-nav",
+ "title": "side-nav",
+ "family": "list-screen",
+ "kind": "component",
+ "document": false,
+ "wrapper": "",
+ "scripts": [],
+ "default": "four-sections-the-first-open",
+ "states": [
+ {
+ "id": "four-sections-the-first-open",
+ "label": "Four sections, the first open",
+ "sampleURLs": [
+ "/settings/billing",
+ "/settings/general",
+ "/settings/members",
+ "/settings/notifications"
+ ]
+ }
+ ]
+ },
{
"name": "dropdown",
"title": "dropdown",
diff --git a/examples/prose.json b/examples/prose.json
index fd132a9..0e90e8f 100644
--- a/examples/prose.json
+++ b/examples/prose.json
@@ -323,6 +323,9 @@
"shell-topbar": {
"blurb": "The topbar shell's own chrome, as markup. The whole page it belongs to is layout-topbar, under Shells."
},
+ "side-nav": {
+ "blurb": "A sidebar inside a panel: a column of links beside the panel's content. In a narrow panel the links become a row that scrolls sideways."
+ },
"signin": {
"blurb": "The sign-in screen in every state it passes through: asking for an address, coming back, after the link is sent, when something went wrong, and the other ways in.",
"states": {
diff --git a/examples/side-nav/four-sections-the-first-open.html b/examples/side-nav/four-sections-the-first-open.html
new file mode 100644
index 0000000..162e862
--- /dev/null
+++ b/examples/side-nav/four-sections-the-first-open.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang="en" dir="ltr">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width">
+<title>side-nav: Four sections, the first open</title>
+<link rel="stylesheet" href="../../css/tokens.css">
+<link rel="stylesheet" href="../../css/themes/day.css">
+</head>
+<body>
+<div rst-side>
+ <nav rst-side-nav aria-label="Settings">
+ <a href="/settings/general" aria-current="page">General</a>
+ <a href="/settings/members">Members</a>
+ <a href="/settings/billing">Billing</a>
+ <a href="/settings/notifications">Notifications</a>
+ </nav>
+ <div>
+ <h2>General</h2>
+ <p>The workspace's name, and the language it opens in.</p>
+ </div>
+</div>
+</body>
+</html>
diff --git a/internal/gallery/heights.go b/internal/gallery/heights.go
index f521f38..b450bdf 100644
--- a/internal/gallery/heights.go
+++ b/internal/gallery/heights.go
@@ -169,6 +169,7 @@ var previewHeights = map[string]int{
"scheme-menu/three-choices-dark-current": 200,
"seg-tabs/buttons-that-switch-in-place": 110,
"seg-tabs/two-sections": 110,
+ "side-nav/four-sections-the-first-open": 210,
"selbox/default": 90,
"shell-sidebar/default": 260,
"shell-topbar/default": 210,
@@ -224,6 +225,7 @@ var previewMobileHeights = map[string]int{
"meter/row": 370,
"page-header/title-subhead-and-a-primary-action": 220,
"pagination/middle-of-a-long-list": 180,
+ "side-nav/four-sections-the-first-open": 300,
"signin/a-passkey": 730,
"signin/after-the-link-is-sent": 600,
"signin/after-the-link-is-sent-in-another-browser": 570,
@@ -286,6 +288,7 @@ var previewMobileLocaleHeights = map[string]map[string]int{
"layout-topbar/default": 1180,
"list-row-action/lead-marker-status-and-an-action": 310,
"modal/default": 350,
+ "side-nav/four-sections-the-first-open": 320,
"signin/after-the-link-is-sent": 630,
"signin/after-the-link-is-sent-in-another-browser": 630,
"signin/google-apple-and-the-rest": 430,
@@ -343,6 +346,7 @@ var previewMobileLocaleHeights = map[string]map[string]int{
"modal/default": 350,
"seg-tabs/buttons-that-switch-in-place": 170,
"seg-tabs/two-sections": 170,
+ "side-nav/four-sections-the-first-open": 320,
"signin/after-the-link-is-sent": 640,
"signin/after-the-link-is-sent-in-another-browser": 640,
"signin/on-the-way-to-keymail": 590,
diff --git a/internal/gallery/i18n.go b/internal/gallery/i18n.go
index be466a0..6904cf8 100644
--- a/internal/gallery/i18n.go
+++ b/internal/gallery/i18n.go
@@ -209,7 +209,8 @@ func leakExempt(en string) bool {
// English does, carried from Rastrillo's tables: the cities of the
// select samples where the language keeps the English name, and the
// product words (Beta, Pro, Plan, Editor, Email, Menu, Slug, Token,
-// No) where it borrows or shares them. Anything else a translated page
+// No) where it borrows or shares them; and General, the settings
+// section Spanish names with the same word. Anything else a translated page
// says in English is a leak, however short. Adding to this list is a
// claim that the translation is right as it stands.
var leakUnchanged = map[string][]string{
@@ -239,15 +240,16 @@ var leakUnchanged = map[string][]string{
"Valletta": {"vi"},
"Zagreb": {"es", "pt-PT", "vi"},
- "Beta": {"es", "pt-PT"},
- "Editor": {"es", "pt-PT"},
- "Email": {"vi"},
- "Menu": {"pt-PT"},
- "No": {"es"},
- "Plan": {"es"},
- "Pro": {"es", "ga", "pt-PT"},
- "Slug": {"es", "pt-PT"},
- "Token": {"es", "pt-PT"},
+ "Beta": {"es", "pt-PT"},
+ "Editor": {"es", "pt-PT"},
+ "Email": {"vi"},
+ "General": {"es"},
+ "Menu": {"pt-PT"},
+ "No": {"es"},
+ "Plan": {"es"},
+ "Pro": {"es", "ga", "pt-PT"},
+ "Slug": {"es", "pt-PT"},
+ "Token": {"es", "pt-PT"},
}
// unchangedIn says whether locale keeps en as it is, by leakUnchanged.
diff --git a/internal/gallery/i18n/ar.json b/internal/gallery/i18n/ar.json
index c61aa33..6a8ea3e 100644
--- a/internal/gallery/i18n/ar.json
+++ b/internal/gallery/i18n/ar.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "مربّع التأشير الذي يرتديه صف القائمة في وضع التحديد. وتسميته تعيد ذكر هويّة الصف.",
"examples.shell-sidebar.blurb": "إطار الهيكل sidebar في صفحة محتوى: على الهاتف، عنصر للرجوع إلى الفهرس.",
"examples.shell-topbar.blurb": "إطار الهيكل ذي الشريط العلوي نفسه، كترميز. والصفحة الكاملة التي ينتمي إليها هي layout-topbar، تحت الهياكل.",
+ "examples.side-nav.blurb": "شريط جانبي داخل لوحة: عمود من الروابط بجانب محتوى اللوحة. في اللوحة الضيقة تصير الروابط صفًا يُمرَّر أفقيًا.",
"examples.signin.blurb": "شاشة تسجيل الدخول في كل حالة تمرّ بها: طلب العنوان، والعودة، وبعد إرسال الرابط، وحين يقع خطأ، والطرق الأخرى للدخول.",
"examples.signin.states.a-passkey": "أسرع طريق للدخول لمن يملكه، ولا شيء يُكتب. وقدّم طريقة ثانية أيضًا: الناس يفقدون أجهزتهم، والمفتاح الذي لا يعمل باب موصد.",
"examples.signin.states.after-the-link-is-sent": "أعد عرض العنوان كما كُتب. فهي الفرصة الوحيدة لملاحظة خطأ مطبعي قبل انتظار رسالة لن تصل أبدًا.",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "ثلاثة خيارات، والداكن هو الحالي.",
"labels.seg-tabs.buttons-that-switch-in-place": "أزرار تبدّل في مكانها",
"labels.seg-tabs.two-sections": "قسمان",
+ "labels.side-nav.four-sections-the-first-open": "أربعة أقسام، والأول مفتوح",
"labels.signin.a-passkey": "مفتاح مرور",
"labels.signin.after-the-link-is-sent": "بعد إرسال الرابط",
"labels.signin.after-the-link-is-sent-in-another-browser": "بعد إرسال الرابط، في متصفح آخر",
diff --git a/internal/gallery/i18n/bn.json b/internal/gallery/i18n/bn.json
index c545093..54a8158 100644
--- a/internal/gallery/i18n/bn.json
+++ b/internal/gallery/i18n/bn.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "নির্বাচন মোডে তালিকার সারি যে চেকবক্সটি পরে। এর লেবেল সারিটির পরিচয় আবার বলে দেয়।",
"examples.shell-sidebar.blurb": "কোনো কনটেন্ট পাতায় sidebar শেলের কাঠামো: ফোনে, সূচিতে ফিরে যাওয়ার একটি নিয়ন্ত্রণ।",
"examples.shell-topbar.blurb": "টপবার শেলের নিজের কাঠামো, মার্কআপ হিসেবে। যে পুরো পাতার অংশ এটি, সেটি layout-topbar, শেলের নিচে।",
+ "examples.side-nav.blurb": "একটি প্যানেলের ভেতরে সাইডবার: প্যানেলের বিষয়বস্তুর পাশে লিঙ্কের একটি কলাম। সরু প্যানেলে লিঙ্কগুলো একটি সারি হয়ে যায়, যা পাশে স্ক্রল করে।",
"examples.signin.blurb": "সাইন-ইন স্ক্রিনের প্রতিটি অবস্থা, যার মধ্য দিয়ে এটি যায়: ঠিকানা চাওয়া, ফিরে আসা, লিঙ্ক পাঠানোর পরে, কিছু ভুল হলে, আর ঢোকার অন্য উপায়গুলো।",
"examples.signin.states.a-passkey": "যাঁর একটি আছে তাঁর জন্য এটিই দ্রুততম পথ, টাইপ করার কিছু নেই। দ্বিতীয় একটি পথও রাখুন: মানুষ যন্ত্র হারায়, আর যে পাসকি কাজ করে না সেটি একটি তালাবদ্ধ দরজা।",
"examples.signin.states.after-the-link-is-sent": "ঠিকানাটি হুবহু ফিরিয়ে দেখান। যে ইমেল কোনওদিন আসবে না তার জন্য অপেক্ষা করার আগে বানান ভুল ধরার এটিই একমাত্র সুযোগ।",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "তিনটি পছন্দ, বর্তমানে গাঢ়।",
"labels.seg-tabs.buttons-that-switch-in-place": "জায়গাতেই বদলানো বোতাম",
"labels.seg-tabs.two-sections": "দুটি বিভাগ",
+ "labels.side-nav.four-sections-the-first-open": "চারটি বিভাগ, প্রথমটি খোলা",
"labels.signin.a-passkey": "একটি পাসকি",
"labels.signin.after-the-link-is-sent": "লিঙ্ক পাঠানোর পরে",
"labels.signin.after-the-link-is-sent-in-another-browser": "লিঙ্ক পাঠানোর পরে, অন্য একটি ব্রাউজারে",
diff --git a/internal/gallery/i18n/drafts.json b/internal/gallery/i18n/drafts.json
index 5c828c1..a3e293b 100644
--- a/internal/gallery/i18n/drafts.json
+++ b/internal/gallery/i18n/drafts.json
@@ -735,6 +735,22 @@
"zh-Hans"
]
},
+ "examples.side-nav.blurb": {
+ "english": "A sidebar inside a panel: a column of links beside the panel's content. In a narrow panel the links become a row that scrolls sideways.",
+ "locales": [
+ "ar",
+ "bn",
+ "es",
+ "ga",
+ "hi",
+ "ja",
+ "pt-PT",
+ "ru",
+ "vi",
+ "yue",
+ "zh-Hans"
+ ]
+ },
"examples.signin.blurb": {
"english": "The sign-in screen in every state it passes through: asking for an address, coming back, after the link is sent, when something went wrong, and the other ways in.",
"locales": [
@@ -1583,6 +1599,22 @@
"zh-Hans"
]
},
+ "labels.side-nav.four-sections-the-first-open": {
+ "english": "Four sections, the first open",
+ "locales": [
+ "ar",
+ "bn",
+ "es",
+ "ga",
+ "hi",
+ "ja",
+ "pt-PT",
+ "ru",
+ "vi",
+ "yue",
+ "zh-Hans"
+ ]
+ },
"labels.snippet.a-line-to-copy": {
"english": "A line to copy",
"locales": [
@@ -1983,6 +2015,22 @@
"zh-Hans"
]
},
+ "samples.General": {
+ "english": "General",
+ "locales": [
+ "ar",
+ "bn",
+ "es",
+ "ga",
+ "hi",
+ "ja",
+ "pt-PT",
+ "ru",
+ "vi",
+ "yue",
+ "zh-Hans"
+ ]
+ },
"samples.Grid": {
"english": "Grid",
"locales": [
@@ -2223,6 +2271,22 @@
"zh-Hans"
]
},
+ "samples.The workspace's name, and the language it opens in.": {
+ "english": "The workspace's name, and the language it opens in.",
+ "locales": [
+ "ar",
+ "bn",
+ "es",
+ "ga",
+ "hi",
+ "ja",
+ "pt-PT",
+ "ru",
+ "vi",
+ "yue",
+ "zh-Hans"
+ ]
+ },
"samples.This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": {
"english": "This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.",
"locales": [
diff --git a/internal/gallery/i18n/es.json b/internal/gallery/i18n/es.json
index 019729a..7554ed6 100644
--- a/internal/gallery/i18n/es.json
+++ b/internal/gallery/i18n/es.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "La casilla de selección que lleva una fila de lista en modo selección. Su etiqueta repite la identidad de la fila.",
"examples.shell-sidebar.blurb": "El armazón del shell sidebar en una página de contenido: en un teléfono, un control para volver al índice.",
"examples.shell-topbar.blurb": "El armazón propio del contenedor con barra superior, como marcado. La página entera a la que pertenece es layout-topbar, en Armazones.",
+ "examples.side-nav.blurb": "Una barra lateral dentro de un panel: una columna de enlaces junto al contenido del panel. En un panel estrecho, los enlaces pasan a ser una fila que se desplaza en horizontal.",
"examples.signin.blurb": "La pantalla de inicio de sesión en cada estado por el que pasa: pidiendo una dirección, al volver, después de enviar el enlace, cuando algo ha ido mal y las otras formas de entrar.",
"examples.signin.states.a-passkey": "La vía más rápida para quien ya tiene una, y no hay nada que teclear. Ofrece también una segunda vía: la gente pierde dispositivos, y una clave de acceso que no funciona es una puerta cerrada.",
"examples.signin.states.after-the-link-is-sent": "Repite la dirección tal cual. Es la única oportunidad de detectar una errata antes de quedarse esperando un correo que nunca llegará.",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "Tres opciones, con Oscuro como actual.",
"labels.seg-tabs.buttons-that-switch-in-place": "Botones que cambian sin salir de la página",
"labels.seg-tabs.two-sections": "Dos secciones",
+ "labels.side-nav.four-sections-the-first-open": "Cuatro secciones, la primera abierta",
"labels.signin.a-passkey": "Una clave de acceso",
"labels.signin.after-the-link-is-sent": "Después de enviar el enlace",
"labels.signin.after-the-link-is-sent-in-another-browser": "Después de enviar el enlace, en otro navegador",
diff --git a/internal/gallery/i18n/ga.json b/internal/gallery/i18n/ga.json
index 55db025..9c15ad8 100644
--- a/internal/gallery/i18n/ga.json
+++ b/internal/gallery/i18n/ga.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "An ticbhosca roghnaithe a chaitheann ró liosta sa mhodh roghnaithe. Athdheimhníonn a lipéad céannacht an ró.",
"examples.shell-sidebar.blurb": "Creatlach na blaoisce sidebar ar leathanach ábhair: ar fhón, rialtán ar ais chuig an innéacs.",
"examples.shell-topbar.blurb": "Fráma féin an bhlaoisc barra barr, mar mharcáil. Is é layout-topbar an leathanach iomlán lena mbaineann sé, faoi Blaoscanna.",
+ "examples.side-nav.blurb": "Barra taoibh laistigh de phainéal: colún nasc in aice le hábhar an phainéil. I bpainéal caol, déantar ró de na naisc a scrollaíonn ó thaobh go taobh.",
"examples.signin.blurb": "An scáileán sínithe isteach i ngach staid a ngabhann sé tríd: ag iarraidh seoladh, ag teacht ar ais, tar éis an nasc a sheoladh, nuair a chuaigh rud éigin mícheart, agus na bealaí eile isteach.",
"examples.signin.states.a-passkey": "An bealach isteach is tapúla don duine a bhfuil ceann aige, gan aon rud le clóscríobh. Tairg dara bealach freisin: cailleann daoine gléasanna, agus is doras faoi ghlas í eochair rochtana nach n-oibríonn.",
"examples.signin.states.after-the-link-is-sent": "Athdheimhnigh an seoladh. Sin an t-aon seans botún clóscríofa a thabhairt faoi deara sula bhfanfaidh duine le ríomhphost nach dtiocfaidh choíche.",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "Trí rogha, Dorcha reatha.",
"labels.seg-tabs.buttons-that-switch-in-place": "Cnaipí a athraíonn san áit",
"labels.seg-tabs.two-sections": "Dhá rannóg",
+ "labels.side-nav.four-sections-the-first-open": "Ceithre rannóg, an chéad cheann ar oscailt",
"labels.signin.a-passkey": "Eochair rochtana",
"labels.signin.after-the-link-is-sent": "Tar éis an nasc a sheoladh",
"labels.signin.after-the-link-is-sent-in-another-browser": "Tar éis an nasc a sheoladh, i mbrabhsálaí eile",
diff --git a/internal/gallery/i18n/hi.json b/internal/gallery/i18n/hi.json
index e78f6ee..a083990 100644
--- a/internal/gallery/i18n/hi.json
+++ b/internal/gallery/i18n/hi.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "चयन मोड में सूची पंक्ति जो चेकबॉक्स पहनती है। उसका लेबल पंक्ति की पहचान दोहराता है।",
"examples.shell-sidebar.blurb": "किसी सामग्री पृष्ठ पर sidebar शेल का ढाँचा: फ़ोन पर, सूची पर वापस जाने का एक नियंत्रण।",
"examples.shell-topbar.blurb": "टॉपबार शेल का अपना ढाँचा, मार्कअप के रूप में। जिस पूरे पेज का यह हिस्सा है, वह layout-topbar है, शेल के अंतर्गत।",
+ "examples.side-nav.blurb": "किसी पैनल के अंदर साइडबार: पैनल की सामग्री के बगल में लिंक का एक कॉलम। संकरे पैनल में लिंक एक पंक्ति बन जाते हैं, जो दाएँ-बाएँ स्क्रॉल होती है।",
"examples.signin.blurb": "साइन-इन स्क्रीन, हर उस स्थिति में जिससे वह गुज़रती है: पता माँगते हुए, लौटने पर, लिंक भेजे जाने के बाद, कुछ गड़बड़ होने पर, और अंदर आने के दूसरे रास्ते।",
"examples.signin.states.a-passkey": "जिसके पास पहले से है, उसके लिए यह सबसे तेज़ रास्ता है और टाइप करने को कुछ नहीं। दूसरा रास्ता भी दें: लोग डिवाइस खो देते हैं, और जो पासकी काम न करे वह बंद दरवाज़ा है।",
"examples.signin.states.after-the-link-is-sent": "पता वापस दिखाएँ। ऐसे ईमेल का इंतज़ार करने से पहले, जो कभी आएगा ही नहीं, टाइपो पकड़ने का यही एकमात्र मौका है।",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "तीन विकल्प, मौजूदा गहरा।",
"labels.seg-tabs.buttons-that-switch-in-place": "उसी जगह बदलने वाले बटन",
"labels.seg-tabs.two-sections": "दो खंड",
+ "labels.side-nav.four-sections-the-first-open": "चार खंड, पहला खुला",
"labels.signin.a-passkey": "एक पासकी",
"labels.signin.after-the-link-is-sent": "लिंक भेजे जाने के बाद",
"labels.signin.after-the-link-is-sent-in-another-browser": "लिंक भेजे जाने के बाद, किसी दूसरे ब्राउज़र में",
diff --git a/internal/gallery/i18n/ja.json b/internal/gallery/i18n/ja.json
index 72d2276..8fd0f5b 100644
--- a/internal/gallery/i18n/ja.json
+++ b/internal/gallery/i18n/ja.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "選択モードで一覧の行が身につけるチェックボックスです。そのラベルは、その行が何であるかを言い直します。",
"examples.shell-sidebar.blurb": "コンテンツページでの sidebar シェルの枠です。スマートフォンでは、目次へ戻る操作になります。",
"examples.shell-topbar.blurb": "トップバーのシェル自身の枠組みを、マークアップとして示したもの。これが属するページ全体は、シェルの下の layout-topbar です。",
+ "examples.side-nav.blurb": "パネルの中のサイドバーです。パネルの内容の横に、リンクを縦一列に並べます。幅の狭いパネルでは、リンクは横一列になり、横にスクロールします。",
"examples.signin.blurb": "サインイン画面が通るすべての状態です。アドレスを尋ねるとき、戻ってきたとき、リンクを送ったあと、何かがうまくいかなかったとき、そしてほかの入りかた。",
"examples.signin.states.a-passkey": "持っている人にとっては最速で、入力するものもありません。第二の方法も用意してください。端末はなくなるものですし、使えないパスキーは施錠された扉です。",
"examples.signin.states.after-the-link-is-sent": "入力されたアドレスをそのまま返して見せます。決して届かないメールを待ち始める前に、打ち間違いに気づける唯一の機会です。",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "3 つの選択肢。現在はダークです。",
"labels.seg-tabs.buttons-that-switch-in-place": "その場で切り替えるボタン",
"labels.seg-tabs.two-sections": "セクションが二つ",
+ "labels.side-nav.four-sections-the-first-open": "セクションが四つ、最初のものを表示中",
"labels.signin.a-passkey": "パスキー",
"labels.signin.after-the-link-is-sent": "リンクを送ったあと",
"labels.signin.after-the-link-is-sent-in-another-browser": "リンクを送ったあと、別のブラウザで",
diff --git a/internal/gallery/i18n/pt-PT.json b/internal/gallery/i18n/pt-PT.json
index 7a6bf3e..df80d0d 100644
--- a/internal/gallery/i18n/pt-PT.json
+++ b/internal/gallery/i18n/pt-PT.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "A caixa de seleção que uma linha de lista usa em modo de seleção. A sua etiqueta repete a identidade da linha.",
"examples.shell-sidebar.blurb": "A moldura do invólucro sidebar numa página de conteúdo: num telemóvel, um controlo para voltar ao índice.",
"examples.shell-topbar.blurb": "A estrutura própria do invólucro com barra superior, como marcação. A página inteira a que pertence é layout-topbar, em Invólucros.",
+ "examples.side-nav.blurb": "Uma barra lateral dentro de um painel: uma coluna de ligações ao lado do conteúdo do painel. Num painel estreito, as ligações passam a ser uma fila que se desloca na horizontal.",
"examples.signin.blurb": "O ecrã de início de sessão em cada estado por que passa: a pedir um endereço, ao voltar, depois de enviada a ligação, quando algo correu mal, e as outras formas de entrar.",
"examples.signin.states.a-passkey": "A via mais rápida para quem já tem uma, e não há nada para escrever. Ofereça também uma segunda via: as pessoas perdem dispositivos, e uma chave de acesso que não funciona é uma porta trancada.",
"examples.signin.states.after-the-link-is-sent": "Repita o endereço tal como foi escrito. É a única oportunidade de detetar uma gralha antes de ficar à espera de um email que nunca chegará.",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "Três opções, com Escuro como atual.",
"labels.seg-tabs.buttons-that-switch-in-place": "Botões que mudam no mesmo sítio",
"labels.seg-tabs.two-sections": "Duas secções",
+ "labels.side-nav.four-sections-the-first-open": "Quatro secções, a primeira aberta",
"labels.signin.a-passkey": "Uma chave de acesso",
"labels.signin.after-the-link-is-sent": "Depois de enviar a ligação",
"labels.signin.after-the-link-is-sent-in-another-browser": "Depois de enviar a ligação, noutro navegador",
diff --git a/internal/gallery/i18n/ru.json b/internal/gallery/i18n/ru.json
index e8f3d8b..e1868c7 100644
--- a/internal/gallery/i18n/ru.json
+++ b/internal/gallery/i18n/ru.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "Флажок выбора, который строка списка носит в режиме выбора. Его подпись повторяет, что это за строка.",
"examples.shell-sidebar.blurb": "Обрамление оболочки sidebar на странице с содержимым: на телефоне это кнопка возврата к оглавлению.",
"examples.shell-topbar.blurb": "Собственная обвязка оболочки с верхней панелью, в виде разметки. Целая страница, к которой она относится, — layout-topbar в разделе «Оболочки».",
+ "examples.side-nav.blurb": "Боковое меню внутри панели: столбец ссылок рядом с содержимым панели. В узкой панели ссылки выстраиваются в строку, которая прокручивается вбок.",
"examples.signin.blurb": "Экран входа во всех состояниях, через которые он проходит: просьба ввести адрес, возвращение, после отправки ссылки, когда что-то пошло не так, и другие способы войти.",
"examples.signin.states.a-passkey": "Самый быстрый вход для того, у кого он есть, и ничего не нужно печатать. Предложите и второй способ: люди теряют устройства, а ключ, который не срабатывает, — это запертая дверь.",
"examples.signin.states.after-the-link-is-sent": "Повторите адрес как есть. Это единственный шанс заметить опечатку до того, как человек начнёт ждать письмо, которое никогда не придёт.",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "Три варианта, выбрана Тёмная.",
"labels.seg-tabs.buttons-that-switch-in-place": "Кнопки, которые переключают на месте",
"labels.seg-tabs.two-sections": "Два раздела",
+ "labels.side-nav.four-sections-the-first-open": "Четыре раздела, открыт первый",
"labels.signin.a-passkey": "Ключ доступа",
"labels.signin.after-the-link-is-sent": "После отправки ссылки",
"labels.signin.after-the-link-is-sent-in-another-browser": "После отправки ссылки, в другом браузере",
diff --git a/internal/gallery/i18n/samples.ar.json b/internal/gallery/i18n/samples.ar.json
index 69606f8..ef6efac 100644
--- a/internal/gallery/i18n/samples.ar.json
+++ b/internal/gallery/i18n/samples.ar.json
@@ -140,6 +140,7 @@
"Frequency": "التكرار",
"From": "من",
"Full name": "الاسم الكامل",
+ "General": "عام",
"Go back": "رجوع",
"Grid": "شبكة",
"Hanoi": "هانوي",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "سيصل هذا بالبريد إلى من أبلغ عن المسألة.",
"The site is being updated or is busy. Try again in a minute.": "يجري تحديث الموقع أو أنه مشغول. حاول مرة أخرى بعد دقيقة.",
"The strip at the top of the shop in March.": "الشريط أعلى المتجر في مارس.",
+ "The workspace's name, and the language it opens in.": "اسم مساحة العمل، واللغة التي تُفتح بها.",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "هذا هو الهيكل column، أحد الهياكل التي تقدّمها Fiesta. الشاشة عمود واحد: ترويسة صفحة، ثم عنوان قسم وبطاقته، ثم الذي يليه. وكل ما تراه هنا هو هذا الهيكل و‎tokens.css‎ ومكوّنان اثنان.",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "هذا هو الهيكل console، أحد الهياكل التي تقدّمها Fiesta. الشاشة عمود واحد: ترويسة صفحة، ثم عنوان قسم وبطاقته، ثم الذي يليه. وكل ما تراه هنا هو هذا الهيكل و‎tokens.css‎ ومكوّنان اثنان.",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "هذا هو الهيكل sidebar، أحد الهياكل التي تقدّمها Fiesta. الشاشة عمود واحد: ترويسة صفحة، ثم عنوان قسم وبطاقته، ثم الذي يليه. وكل ما تراه هنا هو هذا الهيكل و‎tokens.css‎ ومكوّنان اثنان.",
diff --git a/internal/gallery/i18n/samples.bn.json b/internal/gallery/i18n/samples.bn.json
index 1359301..5da182f 100644
--- a/internal/gallery/i18n/samples.bn.json
+++ b/internal/gallery/i18n/samples.bn.json
@@ -140,6 +140,7 @@
"Frequency": "কত ঘনঘন",
"From": "থেকে",
"Full name": "পুরো নাম",
+ "General": "সাধারণ",
"Go back": "ফিরে যান",
"Grid": "গ্রিড",
"Hanoi": "হ্যানয়",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "যিনি এটি জানিয়েছিলেন, তিনি এটি ইমেলে পাবেন।",
"The site is being updated or is busy. Try again in a minute.": "সাইটটি হালনাগাদ হচ্ছে বা ব্যস্ত আছে। এক মিনিট পরে আবার চেষ্টা করুন।",
"The strip at the top of the shop in March.": "মার্চে দোকানের উপরের ফিতা।",
+ "The workspace's name, and the language it opens in.": "ওয়ার্কস্পেসের নাম, আর যে ভাষায় এটি খোলে।",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "এটি column শেল, Fiesta যেসব শেল দেয় তার একটি। একটি পর্দা মানে একটি কলাম: একটি পাতার শিরোনাম, তারপর একটি বিভাগ-শিরোনাম আর তার কার্ড, তারপর পরেরটি। এখানে আপনি যা দেখছেন তার সবটুকুই এই শেল, tokens.css আর দুটি কম্পোনেন্ট।",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "এটি console শেল, Fiesta যেসব শেল দেয় তার একটি। একটি পর্দা মানে একটি কলাম: একটি পাতার শিরোনাম, তারপর একটি বিভাগ-শিরোনাম আর তার কার্ড, তারপর পরেরটি। এখানে আপনি যা দেখছেন তার সবটুকুই এই শেল, tokens.css আর দুটি কম্পোনেন্ট।",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "এটি sidebar শেল, Fiesta যেসব শেল দেয় তার একটি। একটি পর্দা মানে একটি কলাম: একটি পাতার শিরোনাম, তারপর একটি বিভাগ-শিরোনাম আর তার কার্ড, তারপর পরেরটি। এখানে আপনি যা দেখছেন তার সবটুকুই এই শেল, tokens.css আর দুটি কম্পোনেন্ট।",
diff --git a/internal/gallery/i18n/samples.es.json b/internal/gallery/i18n/samples.es.json
index a480608..1c8e0b2 100644
--- a/internal/gallery/i18n/samples.es.json
+++ b/internal/gallery/i18n/samples.es.json
@@ -140,6 +140,7 @@
"Frequency": "Frecuencia",
"From": "Desde",
"Full name": "Nombre completo",
+ "General": "General",
"Go back": "Volver",
"Grid": "Cuadrícula",
"Hanoi": "Hanói",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "Quien informó de esto lo recibirá por correo.",
"The site is being updated or is busy. Try again in a minute.": "El sitio se está actualizando o está ocupado. Inténtalo de nuevo en un minuto.",
"The strip at the top of the shop in March.": "La franja de arriba de la tienda en marzo.",
+ "The workspace's name, and the language it opens in.": "El nombre del espacio de trabajo y el idioma en que se abre.",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Este es el armazón column, uno de los que trae Fiesta. Una pantalla es una columna: una cabecera de página, luego un título de sección con su tarjeta, luego el siguiente. Todo lo que ves aquí es el armazón, tokens.css y dos componentes.",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Este es el armazón console, uno de los que trae Fiesta. Una pantalla es una columna: una cabecera de página, luego un título de sección con su tarjeta, luego el siguiente. Todo lo que ves aquí es el armazón, tokens.css y dos componentes.",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Este es el armazón sidebar, uno de los que trae Fiesta. Una pantalla es una columna: una cabecera de página, luego un título de sección con su tarjeta, luego el siguiente. Todo lo que ves aquí es el armazón, tokens.css y dos componentes.",
diff --git a/internal/gallery/i18n/samples.ga.json b/internal/gallery/i18n/samples.ga.json
index 1526c56..daf550d 100644
--- a/internal/gallery/i18n/samples.ga.json
+++ b/internal/gallery/i18n/samples.ga.json
@@ -140,6 +140,7 @@
"Frequency": "Minicíocht",
"From": "Ó",
"Full name": "Ainm iomlán",
+ "General": "Ginearálta",
"Go back": "Téigh ar ais",
"Grid": "Greille",
"Hanoi": "Hanoi",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "Faigheann an duine a thuairiscigh é seo an freagra trí ríomhphost.",
"The site is being updated or is busy. Try again in a minute.": "Tá an suíomh á nuashonrú nó tá sé gnóthach. Bain triail eile as i gceann nóiméid.",
"The strip at the top of the shop in March.": "An stiall ag barr an tsiopa i mí an Mhárta.",
+ "The workspace's name, and the language it opens in.": "Ainm an spáis oibre, agus an teanga ina n-osclaíonn sé.",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Seo an bhlaosc column, ceann de na blaoscanna a sheolann Fiesta. Is colún é scáileán: ceanntásc leathanaigh, ansin ceannteideal rannóige agus a chárta, ansin an chéad cheann eile. Níl sa mhéid a fheiceann tú anseo ach an bhlaosc, tokens.css agus dhá chomhpháirt.",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Seo an bhlaosc console, ceann de na blaoscanna a sheolann Fiesta. Is colún é scáileán: ceanntásc leathanaigh, ansin ceannteideal rannóige agus a chárta, ansin an chéad cheann eile. Níl sa mhéid a fheiceann tú anseo ach an bhlaosc, tokens.css agus dhá chomhpháirt.",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Seo an bhlaosc sidebar, ceann de na blaoscanna a sheolann Fiesta. Is colún é scáileán: ceanntásc leathanaigh, ansin ceannteideal rannóige agus a chárta, ansin an chéad cheann eile. Níl sa mhéid a fheiceann tú anseo ach an bhlaosc, tokens.css agus dhá chomhpháirt.",
diff --git a/internal/gallery/i18n/samples.hi.json b/internal/gallery/i18n/samples.hi.json
index ae00df1..7929c4b 100644
--- a/internal/gallery/i18n/samples.hi.json
+++ b/internal/gallery/i18n/samples.hi.json
@@ -140,6 +140,7 @@
"Frequency": "आवृत्ति",
"From": "से",
"Full name": "पूरा नाम",
+ "General": "सामान्य",
"Go back": "वापस जाएँ",
"Grid": "ग्रिड",
"Hanoi": "हनोई",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "जिसने यह बताया था, उसे यह ईमेल से मिलेगा।",
"The site is being updated or is busy. Try again in a minute.": "साइट अपडेट हो रही है या व्यस्त है। एक मिनट बाद फिर कोशिश करें।",
"The strip at the top of the shop in March.": "मार्च में दुकान के ऊपर की पट्टी।",
+ "The workspace's name, and the language it opens in.": "वर्कस्पेस का नाम, और वह भाषा जिसमें यह खुलता है।",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "यह column शेल है, Fiesta के शेल में से एक। स्क्रीन एक स्तंभ है: एक पृष्ठ शीर्ष, फिर एक खंड शीर्षक और उसका कार्ड, फिर अगला। यहाँ आप जो कुछ देख रहे हैं, वह यही शेल, tokens.css और दो कंपोनेंट हैं।",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "यह console शेल है, Fiesta के शेल में से एक। स्क्रीन एक स्तंभ है: एक पृष्ठ शीर्ष, फिर एक खंड शीर्षक और उसका कार्ड, फिर अगला। यहाँ आप जो कुछ देख रहे हैं, वह यही शेल, tokens.css और दो कंपोनेंट हैं।",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "यह sidebar शेल है, Fiesta के शेल में से एक। स्क्रीन एक स्तंभ है: एक पृष्ठ शीर्ष, फिर एक खंड शीर्षक और उसका कार्ड, फिर अगला। यहाँ आप जो कुछ देख रहे हैं, वह यही शेल, tokens.css और दो कंपोनेंट हैं।",
diff --git a/internal/gallery/i18n/samples.ja.json b/internal/gallery/i18n/samples.ja.json
index 222747f..2c59a97 100644
--- a/internal/gallery/i18n/samples.ja.json
+++ b/internal/gallery/i18n/samples.ja.json
@@ -140,6 +140,7 @@
"Frequency": "頻度",
"From": "開始日",
"Full name": "氏名",
+ "General": "一般",
"Go back": "戻る",
"Grid": "グリッド",
"Hanoi": "ハノイ",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "報告した本人にメールで届きます。",
"The site is being updated or is busy. Try again in a minute.": "サイトを更新中か、混み合っています。一分ほどしてからお試しください。",
"The strip at the top of the shop in March.": "3月のショップ上部の帯。",
+ "The workspace's name, and the language it opens in.": "ワークスペースの名前と、開いたときの言語。",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "これは column シェルで、Fiesta が備えるシェルのひとつです。画面はひとつの列です。ページヘッダー、次にセクション見出しとそのカード、そして次のもの。ここに見えているのは、このシェルと tokens.css とコンポーネントふたつだけです。",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "これは console シェルで、Fiesta が備えるシェルのひとつです。画面はひとつの列です。ページヘッダー、次にセクション見出しとそのカード、そして次のもの。ここに見えているのは、このシェルと tokens.css とコンポーネントふたつだけです。",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "これは sidebar シェルで、Fiesta が備えるシェルのひとつです。画面はひとつの列です。ページヘッダー、次にセクション見出しとそのカード、そして次のもの。ここに見えているのは、このシェルと tokens.css とコンポーネントふたつだけです。",
diff --git a/internal/gallery/i18n/samples.pt-PT.json b/internal/gallery/i18n/samples.pt-PT.json
index cc91db8..0a585be 100644
--- a/internal/gallery/i18n/samples.pt-PT.json
+++ b/internal/gallery/i18n/samples.pt-PT.json
@@ -140,6 +140,7 @@
"Frequency": "Frequência",
"From": "De",
"Full name": "Nome completo",
+ "General": "Geral",
"Go back": "Voltar",
"Grid": "Grelha",
"Hanoi": "Hanói",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "Quem comunicou isto recebe a resposta por e-mail.",
"The site is being updated or is busy. Try again in a minute.": "O site está a ser atualizado ou está ocupado. Tente de novo dentro de um minuto.",
"The strip at the top of the shop in March.": "A faixa no topo da loja em março.",
+ "The workspace's name, and the language it opens in.": "O nome do espaço de trabalho e o idioma em que abre.",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Este é o invólucro column, um dos que o Fiesta traz. Um ecrã é uma coluna: um cabeçalho de página, depois um título de secção e o seu cartão, depois o seguinte. Tudo o que vê aqui é o invólucro, o tokens.css e dois componentes.",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Este é o invólucro console, um dos que o Fiesta traz. Um ecrã é uma coluna: um cabeçalho de página, depois um título de secção e o seu cartão, depois o seguinte. Tudo o que vê aqui é o invólucro, o tokens.css e dois componentes.",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Este é o invólucro sidebar, um dos que o Fiesta traz. Um ecrã é uma coluna: um cabeçalho de página, depois um título de secção e o seu cartão, depois o seguinte. Tudo o que vê aqui é o invólucro, o tokens.css e dois componentes.",
diff --git a/internal/gallery/i18n/samples.ru.json b/internal/gallery/i18n/samples.ru.json
index ce452f4..ce40db7 100644
--- a/internal/gallery/i18n/samples.ru.json
+++ b/internal/gallery/i18n/samples.ru.json
@@ -140,6 +140,7 @@
"Frequency": "Частота",
"From": "С",
"Full name": "Полное имя",
+ "General": "Общие",
"Go back": "Назад",
"Grid": "Сетка",
"Hanoi": "Ханой",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "Тот, кто сообщил об этом, получит ответ по почте.",
"The site is being updated or is busy. Try again in a minute.": "Сайт обновляется или сильно загружен. Попробуйте снова через минуту.",
"The strip at the top of the shop in March.": "Полоса в верхней части магазина в марте.",
+ "The workspace's name, and the language it opens in.": "Название рабочего пространства и язык, на котором оно открывается.",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Это оболочка column, одна из тех, что поставляет Fiesta. Экран — это колонка: шапка страницы, затем заголовок раздела и его карточка, затем следующий. Всё, что вы здесь видите, — это оболочка, tokens.css и два компонента.",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Это оболочка console, одна из тех, что поставляет Fiesta. Экран — это колонка: шапка страницы, затем заголовок раздела и его карточка, затем следующий. Всё, что вы здесь видите, — это оболочка, tokens.css и два компонента.",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Это оболочка sidebar, одна из тех, что поставляет Fiesta. Экран — это колонка: шапка страницы, затем заголовок раздела и его карточка, затем следующий. Всё, что вы здесь видите, — это оболочка, tokens.css и два компонента.",
diff --git a/internal/gallery/i18n/samples.vi.json b/internal/gallery/i18n/samples.vi.json
index f9a18e3..039632d 100644
--- a/internal/gallery/i18n/samples.vi.json
+++ b/internal/gallery/i18n/samples.vi.json
@@ -140,6 +140,7 @@
"Frequency": "Tần suất",
"From": "Từ",
"Full name": "Họ và tên",
+ "General": "Chung",
"Go back": "Quay lại",
"Grid": "Lưới",
"Hanoi": "Hà Nội",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "Người đã báo việc này sẽ nhận được qua email.",
"The site is being updated or is busy. Try again in a minute.": "Trang web đang được cập nhật hoặc đang bận. Hãy thử lại sau một phút.",
"The strip at the top of the shop in March.": "Dải ở đầu cửa hàng vào tháng Ba.",
+ "The workspace's name, and the language it opens in.": "Tên của không gian làm việc, và ngôn ngữ dùng khi mở nó.",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Đây là khung column, một trong những khung mà Fiesta mang theo. Một màn hình là một cột: đầu trang, rồi một tiêu đề mục cùng thẻ của nó, rồi cái tiếp theo. Tất cả những gì bạn thấy ở đây là khung này, tokens.css và hai thành phần.",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Đây là khung console, một trong những khung mà Fiesta mang theo. Một màn hình là một cột: đầu trang, rồi một tiêu đề mục cùng thẻ của nó, rồi cái tiếp theo. Tất cả những gì bạn thấy ở đây là khung này, tokens.css và hai thành phần.",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "Đây là khung sidebar, một trong những khung mà Fiesta mang theo. Một màn hình là một cột: đầu trang, rồi một tiêu đề mục cùng thẻ của nó, rồi cái tiếp theo. Tất cả những gì bạn thấy ở đây là khung này, tokens.css và hai thành phần.",
diff --git a/internal/gallery/i18n/samples.yue.json b/internal/gallery/i18n/samples.yue.json
index 97c893f..931770c 100644
--- a/internal/gallery/i18n/samples.yue.json
+++ b/internal/gallery/i18n/samples.yue.json
@@ -140,6 +140,7 @@
"Frequency": "頻率",
"From": "由",
"Full name": "全名",
+ "General": "一般",
"Go back": "返去",
"Grid": "格狀",
"Hanoi": "河內",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "報告呢件事嘅人會收到電郵。",
"The site is being updated or is busy. Try again in a minute.": "網站更新緊,或者太多人用緊。等一分鐘再試吓。",
"The strip at the top of the shop in March.": "三月嗰陣商店頂部嘅橫條。",
+ "The workspace's name, and the language it opens in.": "工作區嘅名,同埋佢打開嗰陣用嘅語言。",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "呢個係 column 外殼,Fiesta 帶嘅外殼之一。一個畫面就係一條直欄:頁首,跟住一個分節標題同佢張卡片,再跟住下一個。你喺呢度見到嘅一切,就係呢個外殼、tokens.css 同兩個元件。",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "呢個係 console 外殼,Fiesta 帶嘅外殼之一。一個畫面就係一條直欄:頁首,跟住一個分節標題同佢張卡片,再跟住下一個。你喺呢度見到嘅一切,就係呢個外殼、tokens.css 同兩個元件。",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "呢個係 sidebar 外殼,Fiesta 帶嘅外殼之一。一個畫面就係一條直欄:頁首,跟住一個分節標題同佢張卡片,再跟住下一個。你喺呢度見到嘅一切,就係呢個外殼、tokens.css 同兩個元件。",
diff --git a/internal/gallery/i18n/samples.zh-Hans.json b/internal/gallery/i18n/samples.zh-Hans.json
index fc0cf50..c99ce9c 100644
--- a/internal/gallery/i18n/samples.zh-Hans.json
+++ b/internal/gallery/i18n/samples.zh-Hans.json
@@ -140,6 +140,7 @@
"Frequency": "频率",
"From": "从",
"Full name": "全名",
+ "General": "常规",
"Go back": "返回",
"Grid": "网格",
"Hanoi": "河内",
@@ -323,6 +324,7 @@
"The person who reported this gets it by email.": "提出这条请求的人会收到一封邮件。",
"The site is being updated or is busy. Try again in a minute.": "网站正在更新或者比较繁忙。请过一分钟再试。",
"The strip at the top of the shop in March.": "三月时商店顶部的横条。",
+ "The workspace's name, and the language it opens in.": "工作区的名称,以及它打开时使用的语言。",
"This is the column shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "这是 column 外壳,Fiesta 提供的外壳之一。一个页面就是一列:页眉,然后一个分节标题和它的卡片,然后下一个。你在这里看到的一切,就是这个外壳、tokens.css 和两个组件。",
"This is the console shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "这是 console 外壳,Fiesta 提供的外壳之一。一个页面就是一列:页眉,然后一个分节标题和它的卡片,然后下一个。你在这里看到的一切,就是这个外壳、tokens.css 和两个组件。",
"This is the sidebar shell, one of the shells Fiesta ships. A screen is a column: a page header, then a section heading and its card, then the next one. Everything you see here is the shell, tokens.css and two components.": "这是 sidebar 外壳,Fiesta 提供的外壳之一。一个页面就是一列:页眉,然后一个分节标题和它的卡片,然后下一个。你在这里看到的一切,就是这个外壳、tokens.css 和两个组件。",
diff --git a/internal/gallery/i18n/vi.json b/internal/gallery/i18n/vi.json
index 2945ef8..3450e20 100644
--- a/internal/gallery/i18n/vi.json
+++ b/internal/gallery/i18n/vi.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "Hộp kiểm chọn mà một hàng danh sách mang khi ở chế độ chọn. Nhãn của nó nhắc lại danh tính của hàng.",
"examples.shell-sidebar.blurb": "Bộ khung của shell sidebar trên một trang nội dung: trên điện thoại, đó là nút quay lại trang mục lục.",
"examples.shell-topbar.blurb": "Phần khung riêng của vỏ thanh trên cùng, dưới dạng mã đánh dấu. Trang hoàn chỉnh mà nó thuộc về là layout-topbar, trong mục Khung trang.",
+ "examples.side-nav.blurb": "Một thanh bên nằm trong một khung: một cột liên kết cạnh nội dung của khung. Trong khung hẹp, các liên kết thành một hàng cuộn ngang.",
"examples.signin.blurb": "Màn hình đăng nhập ở mọi trạng thái nó trải qua: hỏi địa chỉ, khi quay lại, sau khi gửi liên kết, khi có gì đó trục trặc, và những cách vào khác.",
"examples.signin.states.a-passkey": "Cách vào nhanh nhất với người đã có, và không phải gõ gì cả. Hãy cung cấp thêm một cách thứ hai: người ta làm mất thiết bị, và một passkey không dùng được là một cánh cửa khoá.",
"examples.signin.states.after-the-link-is-sent": "Hiển thị lại đúng địa chỉ vừa nhập. Đó là cơ hội duy nhất để phát hiện lỗi gõ trước khi ngồi chờ một email không bao giờ tới.",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "Ba lựa chọn, Tối đang được chọn.",
"labels.seg-tabs.buttons-that-switch-in-place": "Nút chuyển đổi ngay tại chỗ",
"labels.seg-tabs.two-sections": "Hai mục",
+ "labels.side-nav.four-sections-the-first-open": "Bốn mục, mục đầu đang mở",
"labels.signin.a-passkey": "Một passkey",
"labels.signin.after-the-link-is-sent": "Sau khi gửi liên kết",
"labels.signin.after-the-link-is-sent-in-another-browser": "Sau khi gửi liên kết, trên một trình duyệt khác",
diff --git a/internal/gallery/i18n/yue.json b/internal/gallery/i18n/yue.json
index 9c3eac4..cc9b46b 100644
--- a/internal/gallery/i18n/yue.json
+++ b/internal/gallery/i18n/yue.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "喺揀選模式下,清單一行戴住嘅勾選框。佢個標籤會將呢行嘅身分再講多次。",
"examples.shell-sidebar.blurb": "sidebar 外殼喺內容頁上嘅框架:喺手機上,係一個返去目錄嘅控制。",
"examples.shell-topbar.blurb": "頂欄外殼自己嘅框架,以標記形式呈現。佢所屬嘅成版係頁面外殼下面嘅 layout-topbar。",
+ "examples.side-nav.blurb": "面板入面嘅側欄:一列連結,喺面板內容旁邊。面板窄嘅時候,啲連結會變成一行,可以左右捲動。",
"examples.signin.blurb": "登入畫面經歷嘅每個狀態:問地址、返嚟、寄咗連結之後、出咗錯嗰陣,同埋其他入嚟嘅方法。",
"examples.signin.states.a-passkey": "對已經有嘅人嚟講係最快,亦冇嘢要打。但都要俾多一個方法:人會唔見咗部機,而一條用唔到嘅通行密鑰就係一道鎖死咗嘅門。",
"examples.signin.states.after-the-link-is-sent": "將個地址原樣顯示返。喺等一封永遠唔會嚟嘅電郵之前,呢個係唯一發現打錯字嘅機會。",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "三個選擇,目前係深色。",
"labels.seg-tabs.buttons-that-switch-in-place": "喺原位切換嘅按鈕",
"labels.seg-tabs.two-sections": "兩個分區",
+ "labels.side-nav.four-sections-the-first-open": "四個分區,第一個打開咗",
"labels.signin.a-passkey": "通行密鑰",
"labels.signin.after-the-link-is-sent": "寄咗連結之後",
"labels.signin.after-the-link-is-sent-in-another-browser": "寄咗連結之後,喺另一個瀏覽器",
diff --git a/internal/gallery/i18n/zh-Hans.json b/internal/gallery/i18n/zh-Hans.json
index 2ba1037..8d41fb8 100644
--- a/internal/gallery/i18n/zh-Hans.json
+++ b/internal/gallery/i18n/zh-Hans.json
@@ -100,6 +100,7 @@
"examples.selbox.blurb": "列表行在选择模式下佩戴的那个勾选框。它的标签会把这一行的身份再说一遍。",
"examples.shell-sidebar.blurb": "sidebar 外壳在内容页上的框架:在手机上,是一个返回目录的控件。",
"examples.shell-topbar.blurb": "顶栏外壳自己的框架,以标记形式呈现。它所属的整页是页面外壳下的 layout-topbar。",
+ "examples.side-nav.blurb": "面板里的侧边栏:一列链接,位于面板内容旁边。面板较窄时,链接变成一行,可以左右滚动。",
"examples.signin.blurb": "登录屏幕经历的每一种状态:询问地址、回来时、链接寄出之后、出了问题时,以及其他进入方式。",
"examples.signin.states.a-passkey": "对已经有通行密钥的人来说这是最快的方式,什么都不用输。但也要提供第二种方式:人会弄丢设备,而一把用不了的通行密钥就是一道锁死的门。",
"examples.signin.states.after-the-link-is-sent": "把地址原样显示回去。这是在苦等一封永远不会来的邮件之前,唯一能发现打错字的机会。",
@@ -373,6 +374,7 @@
"labels.scheme-menu.three-choices-dark-current": "三个选项,当前为深色。",
"labels.seg-tabs.buttons-that-switch-in-place": "原地切换的按钮",
"labels.seg-tabs.two-sections": "两个分区",
+ "labels.side-nav.four-sections-the-first-open": "四个分区,第一个已打开",
"labels.signin.a-passkey": "通行密钥",
"labels.signin.after-the-link-is-sent": "链接寄出之后",
"labels.signin.after-the-link-is-sent-in-another-browser": "链接寄出之后,在另一个浏览器里",
diff --git a/internal/gallery/nav.go b/internal/gallery/nav.go
index abde28f..f2ff878 100644
--- a/internal/gallery/nav.go
+++ b/internal/gallery/nav.go
@@ -44,6 +44,7 @@ var searchTerms = map[string]string{
"person": "avatar user",
"button": "button submit cta",
"snippet": "code copy clipboard",
+ "side-nav": "sidebar navigation sections",
}
// shellNames are the shells the Demos group opens, in the order the