Skip to content

Fix slider focus, playback, menu dismissal and cached icons - #280

Merged
puikinsh merged 41 commits into
masterfrom
codex/2.7.1-accessibility-fixes
Oct 14, 2026
Merged

puikinsh merged 41 commits into
masterfrom
codex/2.7.1-accessibility-fixes

Conversation

@puikinsh

@puikinsh puikinsh commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Mixed-height front-page slides can stretch the CTA into a large empty area. Keyboard navigation also reaches invisible slides, and Escape leaves desktop submenus visible while their toggles announce a collapsed state. Sparkling 2.7.1 adds accessible slider playback, consistent menu state, readable default focus colours and reliable subset-font cache invalidation while preserving saved site settings.

Changes:

  • Inactive slides become inert and recover their original tab order when active.
  • Translated Pause/Play and Space-operable navigation respect focus, hover, visibility and reduced motion. Playback preserves FlexSlider's timer; taller inactive slides cannot cover the controls or stretch the CTA. A flow root isolates their floats; overflow:clip prevents keyboard focus from scrolling the slide area in supporting browsers, with overflow:hidden as a fallback.
  • Menu hover, focus, click and Escape share aria-expanded and aria-controls. Nested-button activation and nearest-submenu Escape run before Bootstrap can intercept them.
  • Default link hover/focus colours meet 4.5:1 on light backgrounds; saved colours remain intact.
  • Subset fonts carry content hashes. Fifteen regression tests run in CI; the ZIP contains only runtime files.

LocalWP validation (2026-10-13):

  • Actual WordPress theme-installer upgrades from verified WordPress.org 2.7.0 to the built ZIP on local-wp: WordPress 7.1.3, PHP 8.5.3, WooCommerce 11.0.1 and Jetpack active.
  • 54 page checks at 375/768/1280px cover page layouts/templates, blocks, blog, post formats, password/comments, archive/search and WooCommerce. Baseline layout/content agree apart from randomized related products; no overflow, broken content images or browser errors.
  • Real nested menus and slider controls tested on jQuery 3.7.1 and 4.0.0 without Migrate. Timed automatic advancement verified on both.
  • Testing exposed and fixed three draft regressions: repeated play() killed autoplay; taller invisible slides blocked playback clicks; Bootstrap intercepted nested-menu Space/Escape.
  • Disabled/empty/single/unlinked sliders, saved/default colours, a conventional child theme, subset/full fonts and theme 404 passed. Reduced motion was simulated in a local-only fixture.
  • Add-to-cart, quantity update, populated mobile cart and checkout rendering passed; cart emptied without submitting an order.
  • 154 installed runtime files matched the tested ZIP; all 49 browser assets returned HTTP 200 and subset fonts loaded.
  • Real WordPress Customizer registration, palette, patterns and layout APIs passed on LocalWP PHP 8.2.29 and 8.5.3. Active theme, saved options and theme mods were unchanged.
  • npm run verify/build passed, with zero lint warnings and 15 passing tests. Autoplay regressions failed before the fix.
  • WordPress Theme Check passed with no required failures or warnings; its recommendations were custom-logo support and registered block styles. All ten GitHub CI checks passed on 3762e74, including the CTA follow-up.

Signed-in follow-up (2026-10-14):

  • Keyboard slider enable/disable updated its dependent controls and live preview. A two-slide configuration saved and persisted.
  • The colour picker restored the new #B02A37 default; keyboard-entered #123456 appeared in preview CSS and persisted after saving.
  • Chrome rendered all four Sparkling patterns without invalid-block recovery prompts. A paragraph was edited and saved as a draft, and the saved text and five root blocks were verified in WordPress. The in-app browser had left the editor's blob iframe blank, so Chrome was used for this check.
  • Ten admin-bar/header measurements passed at 375/768/1280px, including the mobile bar scrolling away and comment anchors remaining below the fixed header.
  • The follow-up logged no PHP fatal errors or theme-path findings. An initial generic Customizer save error succeeded on retry; subsequent colour saving succeeded. Its original cause was not established, and the occurrence is retained here for review.
  • Original Sparkling options, theme mods, widgets, active theme and 2.7.0 theme link were restored. The disposable editor draft was moved to Trash.

CTA follow-up (2026-10-14):

  • Reproduced the oversized CTA on the original 2.7.0: it grew to 883px beneath a short slide because a hidden portrait slide's floats affected Bootstrap's clearing container. The normal desktop height is 117px.
  • Verified three different image heights across 375/768/1280px: the CTA remains 117px on desktop/tablet and approximately 124px on mobile across all nine combinations. Normal animated portrait-to-landscape navigation also preserves the compact CTA.
  • Refined slide containment after focus testing exposed internal scrolling with overflow:hidden. Modern browsers use overflow:clip with display:flow-root; no internal scroll was observed across the nine checks. Overflow:hidden remains the older-browser fallback; that fallback was not separately browser-tested.
  • One-slide, empty-category and disabled-slider cases each keep the normal 117px desktop CTA. These layout checks run in a real LocalWP browser; jsdom does not measure layout.
  • npm run verify/build pass with all 15 regression tests. The updated 154-file ZIP is installed in LocalWP for further review; the original 2.7.0 theme link is preserved separately. The site's active Pato theme and saved settings are unchanged, and the temporary fixture is removed.

The signed-in release-gate checks are complete; this PR is ready for review, not a universal compatibility guarantee. Older WordPress versions, multisite, live Jetpack Photon and arbitrary child overrides were not validated by this run.

Master remains the recovered 2.7.0 baseline from merged #279. This PR is unmerged; WordPress.org is unchanged.

Recorded separately: an existing Kali Forms translation notice, WP-CLI PHP 8.5 console-library deprecations, and a WooCommerce cart dependency warning. A late-created CLI Customizer harness initially lacked its control classes; correcting boot order resolved it. The existing development-only Stylelint/braces advisory remains; npm audit --omit=dev reports zero affected production dependencies.

puikinsh and others added 30 commits October 1, 2026 15:43
2.6.7 subsetted the bundled Font Awesome to the 93 glyphs the theme draws
itself. A hand-added menu icon outside that set -- fa-history, say -- then had
neither a CSS rule nor a glyph, so it rendered as nothing, and the complete
library was reachable only through a PHP filter most sites could not use. The
class syntax was never at fault: fa, fa-fw and the version 4 icon names all
still resolve in Font Awesome 7.

The theme now reads the icons its menus use and loads the complete library by
itself when one of them is missing from the subset, so affected sites recover
on the next page load with no filter and no change to their menu labels.

The scan reads each item's label and its CSS Classes field across all three
registered locations, and counts a class only if it is a real icon name. Icon
names are the rules declaring the --fa custom property, which is what
distinguishes them from utilities: fa-fw, fa-2x and fa-solid are not icons and
no longer force the complete library, and neither does an unrelated class that
happens to start with fa-. Those names are read from the bundled stylesheets
rather than a generated list, so they cannot drift from what actually ships and
no build step is introduced. Reading them is deferred until a menu is known to
carry icons, and the verdict is cached in an option until a menu is saved or
the theme version changes.

Sites whose menus only use the icons the theme already bundles keep the subset,
around 10 KB over the wire against around 250 KB for the complete library.

A Font Awesome icons setting under Content Options covers what the scan cannot
see -- a widget, post content, a page builder -- and sparkling_full_fontawesome
still overrides both. That filter now receives the computed value instead of a
hardcoded false, so it can force the subset as well as the complete set.

Ported from the released 2.6.11 (353e552 in the release copy), which this
repository never received.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.6.11 was released to WordPress.org from a separate copy of the theme, so
this repository still said 2.6.10 and had no changelog entry for it. The
entry is taken verbatim from that release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fe8c62a overwrote the subset stylesheet with the complete Font Awesome 7
build plus the version 4 shim, and added a second copy of the webfonts for it
to resolve against. Every page went back to about 110 KB of icon CSS and
240 KB of fonts, undoing the 2.6.7 subset that 2.6.11 depends on.

- The subset and its inputs (fontawesome.min.css, solid.min.css,
  brands.min.css) are restored byte for byte from 2.6.10, so
  .github/build-icon-subset.py can rebuild it again. The duplicate
  assets/css/fontawesome/webfonts/ directory is gone.
- The complete build that commit introduced is kept as all.min.css, and is
  what the full set now loads: one stylesheet instead of three, whose v4
  shim maps "fa fa-twitter" style classes onto version 7.
- The menu scan reads icon names from all.min.css, the file the complete
  set actually loads, and counts the shim's ".fa.fa-glass" names too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Grunt toolchain (grunt-contrib-imagemin, -compress, grunt-wp-i18n and
friends) is unmaintained and carried the project's npm audit findings. Its
copy task was allow-everything-then-subtract, and its imagemin target pointed
at assets/img/, which this theme does not have. JSCS and JSHint configs are
gone too: JSCS told users to migrate to ESLint years ago.

- package.json scripts: build, verify, lint, lint:fix, i18n, i18n:check,
  version:check and images. The scripts in tools/ are plain Node ESM.
- tools/build-zip.mjs is deny-by-default: a file ships unless EXCLUDE
  matches it, and every hidden file is excluded at any depth.
- tools/check-version.mjs fails when style.css, readme.txt (Stable tag and
  its changelog), changelog.txt and package.json disagree. package.json
  said 3.0 while the theme shipped 2.6.x.
- tools/check-textdomain.mjs allows only 'sparkling' and now ignores
  comments, where prose like "calls esc_html__()" is not a call.
- PHPCS, WPCS and PHPCompatibilityWP are pinned through Composer;
  phpcs.xml.dist applies WordPress-Extra, the sparkling prefix and text
  domain, and PHP 7.4+ compatibility. It replaces phpcs.ruleset.xml, which
  referenced a PHPCompatibility standard nothing installed.
- eslint.config.mjs and .stylelintrc.json lint theme-authored code only.
- .github/workflows/ci.yml replaces .travis.yml (PHP 5.4-7.1, not run since
  Travis withdrew free OSS builds): php -l on 7.4-8.5, PHPCS, ESLint,
  Stylelint, the text domain check and a build artifact.
- Source maps are no longer tracked; none of them was ever shipped.
- .gitignore anchors vendor/ to the root. Unanchored, it also matched
  assets/js/vendor/, which once left Bootstrap and FlexSlider untracked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
phpcbf with phpcs.xml.dist: indentation, spacing, array and control
structure layout, Yoda conditions, and LF line endings for the five files
that still had CRLF. No behaviour change: the rendered HTML of 35 front-end
pages is identical apart from insignificant whitespace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
skip-link-focus-fix.js called element.trigger('focus') -- a jQuery method
-- on a DOM element, so following the skip link or any in-page anchor
raised "TypeError: e.trigger is not a function". The script only existed
for browsers of 2015 that did not move focus to an in-page target; every
browser that runs current WordPress does, and _s dropped it for that reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tested with jQuery 4.0.0 and no Migrate, which is where WordPress core is
heading:

- Bootstrap 3.4.1 refused to run at all ("requires jQuery ... lower than
  version 4"), so the mobile menu never opened. Its version guard now
  accepts 4.x, and collapse/tooltip/popover only test string options with
  /show|hide/: jQuery 4's .data() returns a null-prototype object that a
  RegExp cannot convert, so the navbar toggle threw "Cannot convert object
  to primitive value". The patched file is versioned 3.4.1-sparkling.1.
- assets/js/jquery-compat.js restores what 4.0 removed and Bootstrap or
  FlexSlider call -- $.camelCase (collapse), $.isFunction, $.isWindow --
  plus the deprecated event aliases FlexSlider uses. Every definition is
  guarded, so it does nothing on jQuery 3. Both libraries depend on it.
- flexslider-custom.js started the slider from $( window ).load( fn ), an
  alias jQuery 3.0 removed: it only ever worked through jQuery Migrate and
  threw on jQuery 4. It now listens for load with .on(), or starts at once
  if the page has already loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mping

- The submenu arrow was an empty <a href="#">: no accessible name, no
  open/closed state, and following it added "#" to the address and
  scrolled to the top of the page. Below 1024px it is the only way to open
  a submenu. It is now a <button> with "Show submenu for <item>" and
  aria-expanded, styled to reproduce the old box: at 375 and 1280px the
  menu is pixel-identical. Its tap area is enlarged with an invisible
  ::after rather than by resizing the arrow.
- An open top-level item lost its arrow (Bootstrap's .nav .open > a border
  colours overrode the transparent sides that draw it), and a third-level
  arrow rendered as a dark square; both draw correctly now.
- A parent item whose URL is "#" opens its submenu instead of jumping to
  the top. Escape closes an open submenu and returns focus to its toggle.
- Removed sparkling_add_top_level_menu_url() and the inline footer script
  sparkling_make_top_level_menu_clickable(). The walker already prints each
  item's URL; the script re-ran every top-level click through
  window.location using jQuery's deprecated .click(), only for desktop user
  agents (wp_is_mobile(), which also breaks page caching).
- Menu links no longer repeat their own text in a title attribute when the
  Title Attribute holds icon classes, icons are aria-hidden, the current
  item gets aria-current="page", and titles pass through core's
  nav_menu_item_title filter, which menu icon plugins use.
- The menu button's "Toggle navigation" is translatable, and it carries
  aria-controls and an aria-expanded that follows the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Password form (custom_password_form, kept under its name for child themes):
- It replaced core's form wholesale, losing the redirect back to the post
  and WordPress 6.8's "Invalid password." message; both are back, the
  message tied to the field with aria-describedby.
- The password field was pre-filled with the current search query.
- It posted to get_option( 'siteurl' ) . '/wp-login.php...' unescaped; it
  now uses site_url( ..., 'login_post' ).
- Its button reused the search form's id="searchsubmit" and name="submit".
- Its multi-line markup went through wpautop(), which put <br> and an
  unclosed <p> inside the form. It is built on one line now.

Search form:
- The label pointed at for="s", an id no field had, and every search form
  on a page shared id="searchsubmit" (the 404 page has two forms). Each
  field gets its own wp_unique_id() id for its label.
- The icon-only submit button had no accessible name; it has an
  aria-label now and the icon is aria-hidden.
- The field is type="search", sized like before by giving Bootstrap's
  .form-control its border-box back from the old _s reset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The social menu prints in the footer and in every Sparkling Social widget,
and each copy carried id="menu-social" and id="menu-social-items", so a
page with the footer icons and one widget had both ids twice. The first
copy keeps the ids, for child themes that style them; later copies are
numbered. Every copy also gets a .menu-social class, and the stylesheet's
191 "#menu-social" selectors now read ":is(#menu-social, .menu-social)",
which keeps their id-level specificity: the front page with the footer
icons and two widgets renders pixel-identically at 375 and 1280px.

The <nav> is labelled "Social links", so it is no longer one of several
unnamed navigation landmarks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The site name was an h1 whenever is_home(), so the separate blog page
  had it plus index.php's own page-title h1. It is an h1 only on the
  latest-posts front page now; everywhere else the post, page, archive or
  search title is the h1, and "Nothing Found" steps down to h2 where the
  site name already holds it.
- With a header image, that h1 was display:none and so absent for screen
  readers too; it is visually hidden instead.
- The navigation landmarks are named (Primary, Footer, and Social links
  from the previous commit), the sidebar is "Sidebar", and the three
  footer and three home page widget columns are one named complementary
  region each instead of three unnamed ones.
- "Skip to main content" and the header image's alt text are translatable
  and escaped for their context; the brand link drops a title attribute
  that repeated its text.
- The back-to-top control is a <button> named "Back to top" instead of a
  clickable <div> keyboards could not reach; it looks the same.
- Redundant role="banner"/"contentinfo"/"navigation" attributes on
  elements that already carry those roles are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- content-video.php passed the post's embed through wp_kses_post(), which
  strips <iframe>: a YouTube or Vimeo post rendered an empty
  <div class="entry-video"> on the blog, archive and search pages, and its
  text was skipped too because a video had been found. The embed is
  printed as the_content() renders it.
- Listings printed an empty link wherever a post had no featured image.
  The image link is only printed with an image, and since the title links
  to the same post it is out of the tab order and hidden from screen
  readers instead of being announced twice.
- The byline printed an empty link to /author/ for posts and attachments
  with no author.
- image.php had no #primary or <main>, so attachment pages had no main
  landmark. An image not attached to any post was treated as part of a
  "gallery" of every unattached image in the library; it now links to its
  file. The image link gets the attachment title as its text when the
  image has no alt text, and the image navigation is a named landmark.
- Decorative icons are aria-hidden, and the edit link's icon is the solid
  pen the subset actually carries (fa-regular had no face loaded).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sparkling Categories wrapped post counts in <span>s by replacing every
  "(" and ")" in the list, so a category named "News (Old)" broke the
  markup. Only the count after each link is wrapped now.
- Its "Enable Posts Count" box was read as on whenever the setting
  existed, so unticking it and saving still showed the counts.
- Sparkling Popular Posts printed an empty image link for every post with
  no featured image and no default image; the image link is only printed
  with an image, and kept out of the tab order since the title links to
  the same post.
- All three widgets pass their title through widget_title, like core's
  widgets, and print no empty heading when the title is cleared.
- The default-image picker read the selection from the first ".selected"
  thumbnail in the page, which could belong to another media frame; it
  asks the frame for its selection. Its frame title and button are
  translatable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An Archive Pages headline with any "%" besides the "%s" placeholder --
  "Tagged %s at 100%" -- was a fatal ValueError on PHP 8, because the
  template went through sprintf(); the tag, category, author or date
  archive then died with a critical error. "%s" is now replaced with
  str_replace(), and the result goes through wp_kses_post().
- A classic [caption] with a non-numeric width ("300px") was a fatal
  TypeError: the escaped width string was added to 10. absint() first.
  Its caption text is passed through wp_kses_post() instead of unescaped.
- The front-page call-to-action CSS printed "border-color: ;" when only
  one of its two button colours was set.
- The pingback <link> echoed an unescaped URL from inside echo's argument
  list; it is printed with esc_url() by sparkling_pingback_header(), which
  replaces the unprefixed _s_pingback_header().
- "Untitled" for posts with no title is translatable.
- Removed sparkling_options_display_sidebar(), hooked to the long-gone
  Options Framework's optionsframework_after: it loaded the Facebook and
  Twitter SDKs on a screen that no longer exists.
- Removed the wp_body_open() polyfill; WordPress has had it since 5.2 and
  the theme requires 6.0.
- The footer credit's WordPress link gets rel="noopener".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Slider Category: the default, every category, was not among the
  choices, so the control showed the first category as selected while the
  slider used all of them. "All categories" is a choice now and saves.
- Number of slide items: the control said 3 while an unsaved slider shows
  5; the default is 5. It is a number field, and its sanitizer returns a
  positive whole number instead of nothing for "abc" and "4.7" as-is.
- Typography: the size, font and weight sanitizers fell back to
  $typography_defaults, which was never a global, so an invalid value was
  stored as null with an "array offset on null" warning. The defaults now
  come from sparkling_typography_defaults().
- Removed "Font Subset": it asked for a Google Fonts subset, and the fonts
  have been bundled with the theme since 2.6.11, so it did nothing.
- The Archive Pages headlines were sanitised with esc_html(), so "&"
  came back into the field as "&amp;" on the next visit. They use
  sanitize_text_field() and are escaped on output instead.
- The call-to-action link is a URL field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The on/off switches could not be reached from the keyboard: their
  checkbox was display:none !important, and it had no accessible name (the
  visible title was a <span>, the <label> it pointed at was empty). The
  checkbox is visually hidden instead, named by the title, described by
  the description, and shows a focus ring. Descriptions appear under the
  switch like core controls' do, not in a hover-only tooltip.
- The slider's settings were shown and hidden by inline jQuery
  (.click()/.fadeToggle()) that toggled on every click regardless of
  state, so they fell out of step with the switch after a reset. They use
  an active_callback (sparkling_slider_is_enabled()) now, which also hides
  "link the slides" while the slider is off.
- The "Recommended Actions & Plugins" and "Sparkling Documentation" link
  sections rendered as underlined blue browser links. They are styled
  like core's section titles, with an external-link icon.
- welcome_customizer.css hid the Customizer's "Change" theme button for
  every site; it and welcome_customizer.js are gone. The link section type
  lives in assets/js/customizer-controls.js, and core's plugin installer
  scripts are no longer loaded into the Customizer.
- Removed the inline styles for a sparkling_important_links section that
  no longer exists; the typography select widths moved to customizer.css.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The switches said "Check if you want to enable slider" and "Uncheck this
  option to remove the link from the slides"; they are switches, and the
  second read backwards. They say what they do now: "Show the slider on
  the front page", "Link each slide to its post".
- Eleven colour pickers carried "Default used if no color is selected",
  which explained nothing; they say "Clear it to use the theme default."
- "Top nav dropdown background color" was described as the background of
  the hovered item; that is the next setting's job.
- The body typography font, weight and colour selects had no label at
  all, and the size select was labelled "Main Body Text / Used in p tags".
- Descriptions say what the setting affects (the slider, the
  call-to-action bar, the sticky header, the footer icons, Academicons,
  the WooCommerce layout) instead of "Enter the ..." / "Check to ...".
- The per-post layout box had a two-line title with a note about "Blog
  Type Templates" the theme does not have, and its select had no label.
  It is titled "Layout" with a labelled select in the sidebar for posts,
  pages and products alike; its ids are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- With "Top nav background color" set, every link in an opened dropdown
  got that background, so from 768px -- where submenus open as white
  dropdowns -- each item showed a dark block behind its text, like a text
  selection. The rule only applies below 768px now, where submenus open
  inside the bar.
- A dropdown item's own submenu arrow was floated after its link and
  wrapped under the item whenever the link filled the width; for the last
  item it hung below the dropdown. It sits at the item's right edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "Sticky Header" on:
- For logged-in users the fixed navbar sat at top: 0, under the admin
  bar, which hid its top 32px (46px on phones); the space reserved above
  the content was the difference of the two heights to compensate. The
  navbar now sits below the admin bar, following it when the bar scrolls
  away on narrow screens.
- That space was measured once at load, so it went stale when the menu
  wrapped onto a second row in a narrower window (70px reserved for a
  140px header at 900px). It is re-measured on resize.
- In-page links -- #comments, "Skip to main content", menu links to a
  section -- scrolled their target under the navbar. scroll-padding-top
  is set to the header's bottom edge, so they stop just below it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A block widget's heading -- core's default sidebar is block widgets,
  each a group opening with a heading -- rendered as a large red h2 next
  to the small grey titles of classic widgets. The first heading of a
  block widget is styled as a widget title in the sidebar, footer and
  home widget areas.
- "Reply to Alice" ran into "Cancel reply" with no space between them.
- The "Save my name, email, and website" checkbox sat on its own line
  above its label; they share a line.
- In the footer, the calendar's Bootstrap stripes were near-white behind
  the footer's light text, so every other week was unreadable. The stripe
  is a faint tint of the footer instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured with axe-core on the default colours across the front page,
blog, single post, archive, search and shop pages:

- Sidebar widget text and links were #C1C0C0 on white, 1.8:1 -- every
  category, archive, recent post and popular post date. Now #707070.
- Tag chips were white on #DADADA (1.4:1), the reply button white on
  #C1C0C0 (1.8:1); both use #767676 behind the white text.
- The footer credit links were #777 on #1F1F1F (3.9:1); now #999.
- Bootstrap's #777 table caption (the calendar's month) is #707070.
- The theme red, #DA4453, measured 4.25:1 against white both as link text
  and behind white button text. It is #D83B4B now: the same hue, a shade
  darker, 4.52:1. Sites that saved a colour in the Customizer keep it.
- Links inside running text are underlined, since their colour alone is
  not enough to tell them from the surrounding text.
- The social menu hid each network's name with display:none, so every
  icon link was announced as an unnamed link; the name is visually hidden
  instead. The slider's icon-only previous/next arrows get names, and the
  slider and call-to-action region is a named landmark.

Not changed, for a design decision: the active menu item is the theme red
on the dark navbar (2.7:1), and the comment form's red heading sits on
the grey page background (4.0:1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The social menu draws an icon by matching the link's address, and knew
nothing newer than Telegram: an x.com link -- where twitter.com now
redirects -- or a Bluesky, Threads, Mastodon, TikTok, WhatsApp, Discord,
Reddit, Twitch or Medium link rendered as an empty grey square, and its
empty box sat lower than the icons beside it, pushing the next one out of
line. Those networks have icons and brand hover colours now, and any
unrecognised link gets a generic link icon instead of nothing.

The Font Awesome subset was rebuilt with .github/build-icon-subset.py; it
gains these 11 glyphs (about 1.7 KB) and loses none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The primary menu's walker has always printed an icon class list typed into
a menu item's Title Attribute field ("fa-solid fa-history") as an icon,
and that is the field its documentation pointed people to. The 2.6.11
scan only read the Navigation Label and CSS Classes, so a Title Attribute
icon outside the subset still rendered as nothing. Verified: with only
such an icon in the menu the complete library loads; without it, the
subset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Plugins were looked up at ABSPATH . 'wp-content/plugins/<slug>/<slug>.php',
  which is wrong for a relocated content directory and for any plugin
  whose main file is named differently: FancyBox for WordPress
  (fancybox.php) always showed "Install Now" even when installed. The
  plugin file is found with get_plugins(), and activate/deactivate links
  use it.
- Install, Activate and Deactivate are only offered to users who can carry
  them out; an editor with edit_theme_options but not install_plugins got
  buttons that ended on "Sorry, you are not allowed".
- The "recommended actions" tab was reachable by URL but not linked, and
  opening it was a fatal error ("Using $this when not in object
  context"). It, its two dismiss AJAX handlers, the always-zero action
  counter and menu badge, an AJAX handler that pointed the front page at a
  page titled "Homepage" (no button ever called it), welcome.js, the
  Sparkling_Notify_System class only that tab used, a loading GIF and the
  stylesheet rules for all of it are removed. An unknown tab falls back to
  Getting Started.
- Getting Started's first step pointed at those actions; it points at the
  recommended plugins. The Support tab no longer tells users of a free
  theme to "register your purchase", sends them to the WordPress.org
  forum, and its changelog link opens the Changelog tab.
- The tab templates share the one Sparkling_Welcome instance instead of
  each constructing another, which re-registered every hook. The tabs
  are a labelled <nav> marking the current one, external links say they
  open a new tab, and "About Sparkling" is translatable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- assets/js/customizer.min.js and widget.min.js: the theme enqueues the
  unminified customizer.js and widget.js, so these copies were never
  served -- and had already drifted from their sources.
- bootstrap.css pointed at a bootstrap.css.map the package does not ship.
- The Customizer's link to the About page said "Recommended Actions &
  Plugins"; there are no recommended actions, so it says "Recommended
  Plugins".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WooCommerce colours prices #958E09, 3.4:1 on the theme's white content
box. Shop, related-product and product-page prices use the body text
colour (#6B6B6B, 5.3:1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- theme.json (version 2, so WordPress 6.0 still reads it) carries the
  theme's palette: primary, primary hover, button, button hover, heading,
  body text, background, light and dark, replacing core's default
  palette in the colour pickers. Only block buttons are styled from it:
  they were core's black pill and now use the theme's button colours. Every
  other page renders as before (screenshot-compared at 375 and 1280px).
- Customizer colours write into that palette
  (sparkling_theme_json_customizer_colors(), on wp_theme_json_data_theme):
  Link, Link hover, Element, Element hover, Heading and Body text colour.
  So the editor's pickers, has-*-color classes and block buttons follow a
  site's own colours. Values pass the same sparkling_css_color() check as
  the theme's inline CSS.
- The editor is as wide as the column the post renders in: theme.json's
  648px for posts and pages beside a sidebar or with No Sidebar, 1038px
  for the Full Width layout and the full-width template
  (sparkling_editor_layout_sizes()). Wide and full-width blocks stay in the
  column, as they do on the front end, instead of spanning the canvas.
- $content_width for the full-width template is 1038, the measured
  column; it said 1008, so embeds came out narrower than the column.
- The editor loads the bundled Open Sans and Roboto Slab (it fell back to
  Helvetica and Georgia), and editor-style.css used a blue (#2fa0ec) for
  links and quotes that the theme has never used; it takes the palette.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Four patterns in /patterns, filed under a "Sparkling" category: a
  call-to-action band like the one under the front page slider, three
  feature columns, an image beside text, and a testimonial. They use the
  theme.json palette, so they follow the Customizer's colours. Every
  block in them parses as valid in the editor (wp.blocks.parse).
- "Blocks (full width, no title)" (template-blocks.php) is a canvas for
  them: no title band, featured image or white card, a full-width column
  on a white background, and full-width blocks that reach the edges of
  the window. The title is still the page's h1 for screen readers, and on
  the front page the home widget areas still show. The editor is sized to
  its 1140px column.
- The full-width page templates are listed in one place,
  sparkling_full_width_templates(), which the column classes,
  $content_width and the editor width all read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The theme declared its walker as WP_Bootstrap_Navwalker, unconditionally.
That is also the class name of the walker many plugins bundle, so with
such a plugin active the theme's declaration was a fatal "Cannot declare
class" error on every page. It is Sparkling_Bootstrap_Navwalker now, in a
file named after it, and WP_Bootstrap_Navwalker remains an alias for child
themes that pass it to wp_nav_menu() -- unless something else already
declared that name.

Also, in the walker:
- Submenus no longer carry role="menu", which promised the arrow-key
  behaviour of an application menu that a list of links does not have.
- Dividers are role="separator"; a "disabled" item no longer links to
  "#"; header text is escaped as text rather than as an attribute.
- The "Add a menu" fallback escapes what it prints and shows to users who
  can edit menus (edit_theme_options), not only to administrators.
- Core's walker filters receive $depth, as core passes it; the prefix
  check is disabled on those lines with that reason given.
- sparkling_navwalker_is_icon_class() moves to inc/extras.php, and
  strcasecmp() no longer receives a null Title Attribute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Variables set at the top level of a template are globals, so the coding
standard wants the theme's prefix on them; $plugin in the recommended
plugins tab also overwrote a WordPress global of that name. Renamed with
sparkling_, nothing else changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
puikinsh and others added 9 commits October 1, 2026 16:47
- is_it_woocommerce_page() compared get_the_ID() loosely with each
  WooCommerce page option. On a 404 or an empty search get_the_ID() is
  false, which equalled the 0 of any such option the site did not have,
  so with WooCommerce active those views were laid out as shop pages and
  lost their sidebar. Only a real post ID can match now.
- FlexSlider's CSS and JS loaded on a separate blog page (is_home()),
  where the slider never renders; they load on the front page only.
- register_social_menu() is sparkling_register_social_menu(): it was
  unprefixed and, unlike the theme's other unprefixed functions, not
  pluggable, so it could only collide.
- of_get_option(), get_layout_class(), is_it_woocommerce_page(),
  get_woocommerce_page_id() and the $site_layout, $options_categories and
  $typography_options globals keep their names for child themes; the
  prefix check is disabled on them with that reason. of_get_option()'s
  second parameter is $fallback, not the reserved word $default, and it
  no longer assumes the option is an array.
- Checkbox options are tested for truth rather than == 1, and the bundled
  fonts stylesheet gets a version so browsers refresh it on update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PHPCS (WordPress-Extra, PHPCompatibilityWP) now reports nothing for the
whole theme. Behaviour changes worth knowing:

- The comments heading compared get_comments_number() to the string '1',
  so a filter returning an int printed "1 thoughts"; it compares numbers.
  The heading goes through wp_kses_post() rather than unescaped printf().
- The theme-options <style> block is printed once, only when there is CSS
  to print (a site on the default colours got an empty <style>), with an
  id instead of the obsolete type attribute. It stays on wp_head under
  the same function name, so child themes that unhook it still can.
- The custom header no longer registers callbacks for the pre-Customizer
  Appearance > Header screen (empty CSS rules and an unescaped style
  attribute), and prints "color: #;" no more for an empty colour.
- The Call For Action link control declared 'type' twice; it is a URL
  field as intended.
- The "Enable Posts Count" checkbox uses checked(); widget methods
  declare their visibility; Epsilon_Control_Toggle is a class_alias.
- Loose comparisons are strict, or truth tests where the value is a
  checkbox stored as true, 1 or '1' depending on the release that saved it.
- before_sidebar keeps its _s name, with the reason given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Stylelint: colour hex shorthand, quoting of font-family names, URLs and
  attribute values (stylelint --fix), and the warnings fixed by hand:
  duplicate .entry-meta, .entry-title and .comment-reply-link rules merged
  where nothing between them set the same properties, a selector list that
  named .btn-default:hover twice, an exact duplicate youracclaim.com rule,
  and a "background: transparent" overridden two lines later. The empty
  .bypostauthor rule stays (Theme Check requires the class) with the
  reason given. Twelve front-end pages render identically at 375 and
  1280px before and after.
- ESLint: .github/social-preview.mjs gets browser globals for the code
  it runs in the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
262 strings from the current templates, including the new patterns,
Customizer copy and accessibility labels. tools/make-pot.mjs keeps the
catalog's existing bug-report URL and team address, and accepts WP_CLI
pointing at a WP-CLI binary or wrapper when wp is not on PATH.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The About page's Changelog tab read the theme's own changelog.txt through
WP_Filesystem(), which exists for writing during upgrades: on a host
without direct file access it wants FTP credentials, so the tab said the
file could not be read. It is a plain read of a local file now. Theme
Check flagged the call. Also drops an unused $sparkling variable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readme.txt listed FlexSlider 2.7.0 and Font Awesome 5.1.1; the theme
ships FlexSlider 2.7.2 and Font Awesome 7.3.1. The Open Sans and Roboto
Slab font files bundled since 2.6.11 and Bootstrap's Glyphicons
Halflings font were not listed at all; WordPress.org requires every
bundled resource with its licence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e.md

CLAUDE.md described 2.6.0: no package.json or linters, CRLF line endings,
WP_Bootstrap_Navwalker, a single Font Awesome file and colours only in the
inline CSS. It now covers the npm/Composer commands and the zero-warning
rule, the five places the version lives, the two option stores, the
theme.json palette and editor sizing, the patterns and Blocks template,
the Font Awesome subset and scan, the jQuery 4 patches, the sticky header
and the template wrapper split. readme.md's requirements, feature list
and development section follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@puikinsh
puikinsh marked this pull request as draft October 13, 2026 19:02
@puikinsh
puikinsh marked this pull request as ready for review October 14, 2026 06:26
@puikinsh
puikinsh merged commit 0f92550 into master Oct 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant