diff --git a/.github/workflows/plugin-quality.yml b/.github/workflows/plugin-quality.yml index 1a31959..4079a16 100644 --- a/.github/workflows/plugin-quality.yml +++ b/.github/workflows/plugin-quality.yml @@ -64,6 +64,31 @@ jobs: - run: composer phpunit + browser-smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.2" + coverage: none + + - uses: ramsey/composer-install@v3 + with: + working-directory: plugin/plan-your-day + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + + - run: npm ci + + - run: npx playwright install --with-deps chromium + + - run: npm run browser-smoke + plugin-check: runs-on: ubuntu-latest steps: @@ -79,9 +104,89 @@ jobs: working-directory: plugin/plan-your-day composer-options: "--no-dev --optimize-autoloader" - - uses: wordpress/plugin-check-action@v1 + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Run Plugin Check + env: + WP_ENV_HOME: ${{ runner.temp }}/wp-env + run: | + trap 'status=$?; if [ "$status" -ne 0 ]; then docker ps -a || true; find "$WP_ENV_HOME" -maxdepth 3 -type f -print || true; fi; exit "$status"' EXIT + + plugin_dir="$(realpath ./plugin/plan-your-day)" + + cat > .wp-env.json < "$wp_env_tools/package.json" <<'EOF' + { + "private": true, + "dependencies": { + "@wordpress/env": "11.5.0" + }, + "overrides": { + "rimraf": { + "glob": "^13.0.6" + } + } + } + EOF + npm --prefix "$wp_env_tools" --no-audit --no-fund install + export PATH="$wp_env_tools/node_modules/.bin:$PATH" + command -v wp-env + wp-env --version + cat .wp-env.json + wp-env start --update + wp-env run cli wp cli info + wp-env run cli wp plugin list + wp-env run cli wp plugin list-checks + wp-env run cli wp plugin list-check-categories + + wp-env run cli wp plugin activate plan-your-day + + set +e + wp-env run cli wp plugin check plan-your-day \ + --format=json \ + --ignore-warnings \ + --exclude-files=.distignore,DECISIONS.md,phpcs.xml.dist,phpunit.xml.dist,.wp-env.json \ + --exclude-directories=tests,tools \ + --require=./wp-content/plugins/plugin-check/cli.php \ + > "$RUNNER_TEMP/plugin-check-results.txt" + status=$? + set -e + + cat "$RUNNER_TEMP/plugin-check-results.txt" + if grep -Eq '"type"[[:space:]]*:[[:space:]]*"ERROR"' "$RUNNER_TEMP/plugin-check-results.txt"; then + exit 1 + fi + + exit "$status" + + - uses: actions/upload-artifact@v4 + if: ${{ always() }} with: - build-dir: ./plugin/plan-your-day - exclude-directories: tests,tools - exclude-files: .distignore,DECISIONS.md,phpcs.xml.dist,phpunit.xml.dist - ignore-warnings: true + name: plugin-check-results + path: ${{ runner.temp }}/plugin-check-results.txt + if-no-files-found: ignore + + - name: Stop wp-env + if: ${{ always() }} + env: + WP_ENV_HOME: ${{ runner.temp }}/wp-env + run: | + export PATH="$RUNNER_TEMP/wp-env-tools/node_modules/.bin:$PATH" + if command -v wp-env >/dev/null 2>&1; then + wp-env destroy --force || true + fi diff --git a/.gitignore b/.gitignore index 8484c0d..2589387 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ Thumbs.db node_modules/ vendor/ plugin/plan-your-day/.phpunit.cache/ +playwright-report/ +test-results/ diff --git a/AGENTS.md b/AGENTS.md index 2b400c2..984690e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,10 +2,14 @@ ## Project -This repository contains the standalone **Plan Your Day** WordPress plugin. +This repository contains the standalone **Plan Your Day** WordPress plugin. The +plugin source lives in `plugin/plan-your-day/`. Plan Your Day is a reusable plugin. It must not be branded to, named after, or architecturally tied to any specific client/site. +The latest published release is v0.5. The project is preparing for v1.0 with +focused public-release hardening and cleanup. + ## Hard rules - Do not reference DKC, Destination Kona Coast, or any other site-specific branding in code, comments, docs, settings, namespaces, text domains, admin labels, or frontend copy. @@ -15,30 +19,51 @@ Plan Your Day is a reusable plugin. It must not be branded to, named after, or a - Inspect the existing code before editing. - Do not introduce a new build system unless absolutely necessary. - Do not remove accessibility features. +- Do not introduce Font Awesome, icon fonts, bundled icon images, or SVG icon + assets for UI chrome. Use CSS-only details for carets, grips, toggles, and + similar controls unless the asset is real content. +- Keep the bundled Noto Sans font files and their license together. Do not add + or replace fonts without confirming license coverage. - Escape and sanitize all WordPress output/input appropriately. - Do not allow raw HTML/JS injection through admin-editable copy/category fields unless a deliberate sanitized rich-text pattern already exists. -## Current feature direction +## Current product surface The plugin currently supports: - frontend place/category/custom searches, +- "Get more results" pagination for category and custom searches, - waypoint selection, +- waypoint reorder/remove and clear-trip behavior, - route/directions behavior, -- admin-editable interface copy, -- admin-editable/custom categories. +- map preview and Google Maps handoff behavior, +- distance labels and distance-unit settings, +- frontend color mode switching with an admin default, +- Noto Sans frontend/admin typography, +- admin-editable interface copy where the setting is still useful, +- admin-editable/custom categories with draggable ordering. + +## Current v1.0 direction + +Prioritize release readiness over new feature breadth. Current hardening lanes +include: + +- CSS-only UI details and removal of bundled icon assets. +- REST token bootstrap cache safety. +- Rate-limit state moving to expiring transients. +- Google geocode failure handling. +- Stable, quiet WordPress Plugin Check workflow coverage. -Current planned features include: -- “Get more results” for category and custom searches, -- a mobile fixed waypoint-count tab, -- distance preference controls, -- transportation preference controls for walking, driving, or both, -- a later compacting pass after copy and category settings are cleaned up. +PHPStan is not part of the current v1.0 gate. A one-off scan without WordPress +stubs reports missing WordPress symbols, so do not add PHPStan to CI unless the +project also adds reproducible WordPress-aware tooling. ## UI/accessibility expectations - Use real buttons for interactive controls. - Preserve keyboard access. - Preserve accessible names for controls. +- Preserve color mode behavior: admin default first, system preference only + when configured, and explicit frontend user choice as the strongest signal. - Loading/error/status messages should be understandable to screen reader users. - Do not steal focus unexpectedly. - Use polite live/status announcements where helpful, but do not announce every result one-by-one. @@ -57,7 +82,17 @@ Current planned features include: ## Testing expectations -Before finishing a task, check: +Before finishing a code task, choose checks that match the change. Common +checks include: + +- `cd plugin/plan-your-day && composer test` +- `npm ci && npx playwright install chromium && npm run browser-smoke` +- `find plugin/plan-your-day -name '*.php' -print -exec php -l {} \;` +- GitHub `Plugin Quality` workflow for PHP syntax, PHPCS, PHPUnit, browser + smoke, and WordPress Plugin Check. + +Also check, as relevant: + - frontend still renders with default settings, - saved settings still load, - category/custom searches still work, @@ -67,4 +102,4 @@ Before finishing a task, check: - no client/site-specific references were introduced. ## Additional Rules -- when referring to an Issue on a GitHub repo, capitalize the I. \ No newline at end of file +- When referring to an Issue on a GitHub repo, capitalize the I. diff --git a/README.md b/README.md index 6ce452d..920e371 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ -# Plan Your Day +# Waypoints -Plan Your Day is a configurable WordPress plugin for building day-trip planners -with Google Maps and Places data. The plugin source lives in +Waypoints is a configurable WordPress plugin for building day-trip planners with +Google Maps and Places data. The plugin source lives in `plugin/plan-your-day/`. ## Status -The plugin is under active development. Completed foundation work includes: +The latest published release is **v0.5**. The project is in v1.0 release +preparation, with UI polish and final hardening work happening in focused pull +requests. + +Completed foundation work includes: - WordPress plugin scaffold, activation/deactivation hooks, uninstall routine, release metadata, and Composer PSR-4 autoloading. @@ -25,14 +29,28 @@ The plugin is under active development. Completed foundation work includes: shared planner renderer. - WordPress REST browse/route endpoints with POST-only requests, guest-safe visitor token validation, same-site request checks, trusted-proxy-aware client - IP resolution, and file-backed rate limiting. + IP resolution, and configurable rate limiting. - Frontend JavaScript that updates browse and trip state through REST instead of full-page planner reloads. - Admin-editable categories, admin-editable interface copy, cache-clear tools, and a Google API test tool. +- Browser smoke coverage for shortcode and block renders, category browsing, + load-more, waypoint add/reorder/remove, clear-trip behavior, focus recovery, + and narrow-viewport start-options behavior. +- GitHub Actions quality checks for PHP syntax, PHPCS, PHPUnit, browser smoke, + and WordPress Plugin Check. + +Current v1.0 hardening work is split into open PRs for: -Browser automation and broader production QA are still tracked in GitHub -issues. +- Removing bundled icon assets in favor of CSS-only UI details. +- Making REST token bootstrap cache behavior safer. +- Moving rate-limit state to expiring transients. +- Hardening Google geocode failure handling. +- Keeping the Plugin Check workflow stable and quiet. + +PHPStan is not part of the current v1.0 gate. A one-off scan without WordPress +stubs only reports missing WordPress symbols, so the project is relying on the +checks above for this release. ## Requirements @@ -55,6 +73,14 @@ issues. - `docs/` contains installation, usage, admin, release, architecture, settings, security, troubleshooting, and historical planning notes for the plugin. +## Naming Note + +The public plugin name is **Waypoints**. The source still uses `plan-your-day` +for the plugin directory and text domain, `[plan_your_day]` for the shortcode, +`plan_your_day_*` for settings/options/hooks, `Acodebeard\PlanYourDay` for the +PHP namespace, and related REST, asset, and CSS identifiers because the plugin +was renamed after those compatibility surfaces already existed. + ## Documentation Start with [docs/README.md](docs/README.md). Current docs cover installation, @@ -71,8 +97,8 @@ security, and troubleshooting. composer install ``` -3. Activate **Plan Your Day** from the WordPress Plugins screen. -4. Open **Settings > Plan Your Day**. +3. Activate **Waypoints** from the WordPress Plugins screen. +4. Open **Settings > Waypoints**. 5. Configure the required default location and Google API keys. Release zips should include generated Composer autoload files so production @@ -86,7 +112,7 @@ From `plugin/plan-your-day/`, build an installable WordPress admin zip with: ./tools/build-release-zip.sh ``` -The script creates `dist/plan-your-day-0.1.0.zip` at the repository root, +The script creates `dist/plan-your-day-0.5.zip` at the repository root, installs production-only Composer autoload files into a temporary staging copy, and packages the final artifact with a top-level `plan-your-day/` directory suitable for **Plugins > Add New > Upload Plugin**. @@ -127,7 +153,22 @@ Server-side Google API keys must not be exposed to frontend runtime config. ## Useful Checks -Run PHP syntax checks from the repository root: +Run the PHP checks from the plugin directory: + +```sh +cd plugin/plan-your-day +composer test +``` + +Run the browser smoke suite from the repository root: + +```sh +npm ci +npx playwright install chromium +npm run browser-smoke +``` + +Run a quick PHP syntax-only sweep from the repository root: ```sh find plugin/plan-your-day -name '*.php' -print -exec php -l {} \; @@ -140,4 +181,5 @@ rg "localhost|example\\.test|Kona|pier" plugin/plan-your-day ``` The plugin PHPUnit suite covers current service, settings, and activation -behavior. Browser and broader integration QA are still tracked separately. +behavior. The GitHub `Plugin Quality` workflow also runs WordPress Plugin Check +against an installable wp-env-backed plugin checkout. diff --git a/docs/ADMIN.md b/docs/ADMIN.md index d6959e9..3645edd 100644 --- a/docs/ADMIN.md +++ b/docs/ADMIN.md @@ -1,9 +1,9 @@ # Admin Workflows -Plan Your Day settings live under: +Waypoints settings live under: ```text -Settings > Plan Your Day +Settings > Waypoints ``` Only administrators with `manage_options` can change plugin settings or run the @@ -73,8 +73,8 @@ Each category row includes: - enabled state - sort order -The starter category fallback setting controls whether fresh or empty installs -show the built-in generic starter rows instead of no category buttons. +Fresh installs start with the built-in starter rows. Administrators can delete +all saved rows when they want visitors to use custom search only. ## Google API And Cache diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 30c4b16..6c687f2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Architecture -Plan Your Day is a generic WordPress plugin for building configurable day-trip +Waypoints is a generic WordPress plugin for building configurable day-trip planners with Google Maps and Places data. ## Plugin Entry Point @@ -46,7 +46,7 @@ plan_your_day_settings The admin page is registered under: ```text -Settings > Plan Your Day +Settings > Waypoints ``` The admin screen currently exposes required default location fields, planner @@ -96,8 +96,7 @@ Maps Embed key is separate. Planner helpers extracted so far: - `CategoryCatalog`: owns the built-in starter category list for fresh installs - and empty saved-category fallbacks, then filters the saved category rows down - to the enabled frontend catalog. + and filters the saved category rows down to the enabled frontend catalog. - `PlaceParser`: shapes Google place responses and sanitizes Place IDs / HTTPS map URLs. - `WaypointList`: normalizes, deduplicates, caps, and reorders waypoint IDs. @@ -136,10 +135,10 @@ Built-in starter rows for fresh installs live in: plugin/plan-your-day/src/Planner/CategoryCatalog.php ``` -Use `CategoryCatalog::default_rows()` when changing the starter list for new or -empty installs. `Settings::get_categories()` resolves the saved rows first and -falls back to that starter list only when the saved category list is empty and -the fallback toggle remains enabled. +Use `CategoryCatalog::default_rows()` when changing the starter list for fresh +installs. `Settings::get_categories()` returns the saved list as-is, including an +intentionally empty list, so visitors can use custom search without category +buttons. ## Security Helpers diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 3f206c2..d05f1c6 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -35,6 +35,9 @@ Then activate the plugin from the WordPress admin Plugins screen or with WP-CLI: wp --path=/path/to/wordpress plugin activate plan-your-day ``` +In the WordPress admin, the plugin appears as **Waypoints**. Open +**Settings > Waypoints** after activation. + ## Local Test Install One local development setup can look like this: @@ -91,5 +94,5 @@ cd plugin/plan-your-day ./tools/build-release-zip.sh ``` -The script writes `dist/plan-your-day-0.1.0.zip` at the repository root, which +The script writes `dist/plan-your-day-0.5.zip` at the repository root, which can be uploaded through the WordPress Plugins screen. diff --git a/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md b/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md index e86ce90..87d8065 100644 --- a/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md +++ b/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md @@ -2,6 +2,10 @@ GitHub issue: https://github.com/acodebeard/plan-your-day/issues/20 +Historical note: this was the original scaffold plan from before the public +plugin name changed to Waypoints. Treat the names in this file as historical +scaffold context, not current public naming guidance. + ## Summary Create the initial WordPress plugin scaffold for Plan Your Day. The scaffold should define the plugin directory structure, bootstrap file, plugin headers, namespace, constants, activation and deactivation hooks, version options, and release metadata placeholders. diff --git a/docs/QA.md b/docs/QA.md new file mode 100644 index 0000000..b66c6c4 --- /dev/null +++ b/docs/QA.md @@ -0,0 +1,44 @@ +# Frontend QA + +Waypoints keeps a lightweight browser smoke suite for the public planner +frontend. The suite focuses on the shipped shortcode and block entry points, the +same-site REST flow, and a small set of accessibility-sensitive interactions. + +## Automated Smoke Coverage + +- Plain-page asset scoping: the planner CSS/JS do not load on pages without a + planner render. +- Shortcode render: the frontend loads, the category browse flow works, and the + public JS boots without browser-console errors. +- Block render: the dynamic block renders through the same frontend JS path and + can browse categories successfully. +- Core trip flow: category browse, load-more, add waypoint, reorder, remove, + and clear-trip actions complete through the real REST endpoints. +- Focus-sensitive behavior: route mutations restore focus to a useful control + after add, reorder, remove, and clear-trip actions. +- Narrow-viewport interaction: the start-options toggle can be collapsed and + reopened in a mobile-sized viewport. + +The browser harness uses a tiny PHP app and deterministic fake Google data. It +does not replace WordPress integration or external Google API validation; it is +meant to catch frontend regressions in the plugin's own public flow. + +## Local Run + +From the repo root: + +```bash +cd plugin/plan-your-day +composer install +cd ../.. +npm ci +npx playwright install chromium +npm run browser-smoke +``` + +## CI Expectation + +The GitHub `Plugin Quality` workflow runs the browser smoke suite in the +`browser-smoke` job. Public frontend changes should keep that job green and +should update the smoke harness when entry points, selectors, or planner +interaction flow change. diff --git a/docs/README.md b/docs/README.md index 9b8c02e..9521188 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,9 @@ -# Plan Your Day Documentation +# Waypoints Documentation This directory documents the WordPress plugin as it exists today. The plugin is installable and has admin/settings, Google API, cache, planner helper, planner -state, shortcode and block rendering, REST endpoints, assets, and release zip -packaging in place. Broader production QA automation is still in progress. +state, shortcode and block rendering, REST endpoints, assets, release zip +packaging, and browser smoke automation in place. ## Current Documents @@ -15,6 +15,8 @@ packaging in place. Broader production QA automation is still in progress. tools, and the Google API test workflow. - [Release Process](RELEASES.md): version metadata alignment, changelog expectations, and the manual GitHub release zip workflow. +- [Frontend QA](QA.md): browser smoke coverage, local run steps, and ongoing + public frontend QA expectations. - [Architecture](ARCHITECTURE.md): plugin layers, current service boundaries, and current runtime structure. - [Settings](SETTINGS.md): option name, settings groups, defaults, API keys, @@ -31,7 +33,6 @@ land: - REST endpoint request/response reference. - Anonymous visitor token details. -- Production QA and accessibility notes. ## Historical Planning Notes @@ -40,3 +41,4 @@ truth for implementation status: - [Plugin TODO Snapshot](PLAN-YOUR-DAY-PLUGIN-TODO.md) - [GitHub Issue Draft Snapshot](PLAN-YOUR-DAY-PLUGIN-ISSUES.md) +- [Issue 20 Plugin Scaffold Plan](ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md) diff --git a/docs/RELEASES.md b/docs/RELEASES.md index 0956005..c92aaff 100644 --- a/docs/RELEASES.md +++ b/docs/RELEASES.md @@ -1,7 +1,7 @@ # Release Process -Plan Your Day v1 ships as a manual GitHub release zip. The plugin does not -include a private updater or a custom `Update URI` workflow right now. +Waypoints v1 ships as a manual GitHub release zip. The plugin does not include a +private updater or a custom `Update URI` workflow right now. ## Release Metadata diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 6675945..d2edf6f 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -1,6 +1,6 @@ # Settings Reference -Plan Your Day stores plugin configuration in: +Waypoints stores plugin configuration in: ```text plan_your_day_settings @@ -63,7 +63,7 @@ Each saved category row includes: - `enabled` - `sort_order` -Built-in starter rows for fresh installs and empty-list fallbacks live in: +Built-in starter rows for fresh installs live in: ```text plugin/plan-your-day/src/Planner/CategoryCatalog.php @@ -71,9 +71,9 @@ plugin/plan-your-day/src/Planner/CategoryCatalog.php Important behavior: -- `Settings::get_categories()` returns the saved category rows when any exist. -- If the saved list is empty and `use_preset_categories` remains enabled, the - plugin falls back to the built-in starter rows. +- `Settings::get_categories()` returns the saved category rows as-is, including + an intentionally empty list. +- Fresh installs seed the default rows; upgrades preserve the saved list. - `CategoryCatalog` filters those rows down to enabled frontend categories and keys them by slug for renderer and planner-state use. - `Settings::sanitize_categories()` trims text fields, strips HTML/JS, creates diff --git a/docs/USAGE.md b/docs/USAGE.md index f532326..5ba3692 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1,15 +1,15 @@ # Frontend Usage -Plan Your Day can be rendered through either the block editor or the shortcode. -Both entry points use the same server-rendered planner and the same frontend -CSS and JavaScript assets. +Waypoints can be rendered through either the block editor or the shortcode. Both +entry points use the same server-rendered planner and the same frontend CSS and +JavaScript assets. ## Before You Place The Planner Make sure the plugin has been configured under: ```text -Settings > Plan Your Day +Settings > Waypoints ``` At minimum, set: @@ -26,7 +26,7 @@ instead of the full planner. The plugin registers a dynamic block named: ```text -Plan Your Day +Waypoints ``` Editor behavior: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..63106c7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,76 @@ +{ + "name": "plan-your-day-qa", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "plan-your-day-qa", + "devDependencies": { + "@playwright/test": "1.59.1" + } + }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..653b1de --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "plan-your-day-qa", + "private": true, + "description": "Browser smoke tests for the Plan Your Day plugin repository.", + "scripts": { + "browser-smoke": "playwright test" + }, + "devDependencies": { + "@playwright/test": "1.59.1" + } +} diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..3921fa0 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,26 @@ +const { defineConfig } = require('@playwright/test'); + +const baseURL = process.env.PLAN_YOUR_DAY_BROWSER_BASE_URL || 'http://127.0.0.1:9080'; + +module.exports = defineConfig({ + testDir: './tests/browser', + fullyParallel: false, + timeout: 30_000, + expect: { + timeout: 5_000, + }, + use: { + baseURL, + trace: 'on-first-retry', + }, + webServer: { + command: + 'mkdir -p tmp && rm -f tmp/plan-your-day-browser-state.json && ' + + `PLAN_YOUR_DAY_BROWSER_BASE_URL=${baseURL} ` + + 'PLAN_YOUR_DAY_BROWSER_STATE_FILE=tmp/plan-your-day-browser-state.json ' + + 'php -S 127.0.0.1:9080 -t . plugin/plan-your-day/tests/browser-app/router.php', + url: `${baseURL}/__health`, + reuseExistingServer: !process.env.CI, + timeout: 30_000, + }, +}); diff --git a/plugin/plan-your-day/.distignore b/plugin/plan-your-day/.distignore index 56acbeb..c738030 100644 --- a/plugin/plan-your-day/.distignore +++ b/plugin/plan-your-day/.distignore @@ -8,11 +8,13 @@ /.gitignore /.github /dist +/docs /.phpunit.cache /node_modules /vendor /tests /tools +/phpstan.neon /phpcs.xml.dist /phpunit.xml /phpunit.xml.dist diff --git a/plugin/plan-your-day/assets/css/admin-settings.css b/plugin/plan-your-day/assets/css/admin-settings.css index d0b67d5..1ae01c6 100644 --- a/plugin/plan-your-day/assets/css/admin-settings.css +++ b/plugin/plan-your-day/assets/css/admin-settings.css @@ -1,17 +1,49 @@ +@font-face { + font-family: "Noto Sans"; + src: url("../fonts/noto-sans-v42-latin-ext-300.woff2") format("woff2"); + font-style: normal; + font-weight: 300; + font-display: swap; +} + +@font-face { + font-family: "Noto Sans"; + src: url("../fonts/noto-sans-v42-latin-ext-regular.woff2") format("woff2"); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: "Noto Sans"; + src: url("../fonts/noto-sans-v42-latin-ext-700.woff2") format("woff2"); + font-style: normal; + font-weight: 700; + font-display: swap; +} + .plan-your-day-admin-page { + font-family: "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; scroll-behavior: smooth; } +.plan-your-day-admin-page input, +.plan-your-day-admin-page select, +.plan-your-day-admin-page textarea, +.plan-your-day-admin-page button { + font-family: inherit; +} + .plan-your-day-admin-accordion { display: grid; - gap: 1rem; + gap: 0.65rem; max-width: 72rem; - margin-top: 1rem; + margin-top: 0.75rem; } .plan-your-day-admin-accordion__item { margin: 0; - border: 1px solid #ccd0d4; + border: thin solid #ccd0d4; border-radius: 12px; background: #fff; box-shadow: 0 1px 2px rgba(16, 24, 40, 0.08); @@ -22,8 +54,8 @@ display: flex; align-items: flex-start; justify-content: space-between; - gap: 1rem; - padding: 1rem 1.25rem; + gap: 0.75rem; + padding: 0.75rem 1rem; cursor: pointer; list-style: none; } @@ -47,7 +79,7 @@ .plan-your-day-admin-accordion__summary-copy { display: grid; - gap: 0.35rem; + gap: 0.2rem; } .plan-your-day-admin-accordion__title { @@ -61,31 +93,195 @@ .plan-your-day-admin-accordion__description { display: block; font-size: 0.875rem; - line-height: 1.5; + line-height: 1.4; color: #50575e; } .plan-your-day-admin-accordion__panel { - padding: 0 1.25rem 1.25rem; - border-top: 1px solid #e2e4e7; + padding: 0 1rem 1rem; + border-top: thin solid #e2e4e7; } .plan-your-day-interface-copy-group { display: grid; - gap: 1rem; + gap: 0.5rem; max-width: 56rem; - padding-top: 1rem; + padding-top: 0.75rem; +} + +.plan-your-day-interface-copy-field { + padding: 0.55rem 0.65rem; + border: thin solid transparent; + border-radius: 8px; } .plan-your-day-interface-copy-field-label { - display: block; - margin-bottom: 0.35rem; + display: flex; + align-items: center; + gap: 0.35rem; + flex-wrap: wrap; + margin-bottom: 0.2rem; font-weight: 600; } -.plan-your-day-categories-editor .widefat th:first-child, -.plan-your-day-categories-editor .widefat td:first-child { +.plan-your-day-admin-page .form-table .plan-your-day-compact-checkbox-field > th { + width: 13rem; + max-width: 13rem; + padding-right: 1rem; +} + +.plan-your-day-categories-editor { + max-width: 100%; + overflow-x: auto; +} + +.plan-your-day-categories-editor .widefat { + min-width: 62rem; + table-layout: auto; +} + +.plan-your-day-category-order-col, +.plan-your-day-category-enabled-col, +.plan-your-day-category-delete-col { + width: 1%; +} + +.plan-your-day-category-label-col { + width: 10rem; +} + +.plan-your-day-category-query-col { + width: 14rem; +} + +.plan-your-day-category-order-column { + width: 1%; +} + +.plan-your-day-category-enabled-column { + width: 1%; +} + +.plan-your-day-category-label-column { + width: 10rem; +} + +.plan-your-day-category-query-column { + width: 14rem; +} + +.plan-your-day-category-delete-column { + width: 1%; +} + +.plan-your-day-category-order-column, +.plan-your-day-category-enabled-column, +.plan-your-day-category-delete-column, +.plan-your-day-category-order-cell { + width: 1%; + white-space: nowrap; + text-align: center; +} + +.plan-your-day-categories-editor .plan-your-day-category-order-column, +.plan-your-day-categories-editor .plan-your-day-category-enabled-column, +.plan-your-day-categories-editor .plan-your-day-category-order-cell, +.plan-your-day-categories-editor .plan-your-day-category-enabled-cell { + padding-right: 0.45rem; + padding-left: 0.45rem; +} + +.plan-your-day-category-enabled-cell { + width: 1%; + white-space: nowrap; + text-align: center; +} + +.plan-your-day-category-label-cell input, +.plan-your-day-category-description-cell textarea, +.plan-your-day-category-query-cell input { + width: 100%; + max-width: 100%; + box-sizing: border-box; +} + +.plan-your-day-category-drag-handle { + appearance: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2.25rem; + margin: 0; + padding: 0; + border: 0; + border-radius: 4px; + background: transparent; + box-shadow: none; + color: #50575e; + cursor: grab; + position: relative; + vertical-align: middle; +} + +.plan-your-day-category-drag-handle::before { + content: ""; + display: block; + width: 18px; + height: 18px; + background-image: repeating-radial-gradient(circle, currentColor 0 2px, transparent 2.5px 100%); + background-position: center; + background-size:6px 6px; +} + +.plan-your-day-category-drag-handle:hover { + color: #1d2327; +} + +.plan-your-day-category-drag-handle:focus-visible { + outline: 2px solid #2271b1; + outline-offset: 2px; +} + +.plan-your-day-category-row.is-dragging { + opacity: 0.55; +} + +.plan-your-day-category-row.is-dragging .plan-your-day-category-drag-handle { + cursor: grabbing; +} +.plan-your-day-category-row.is-dragging .plan-your-day-category-drag-handle::before { + transform: scale(1.5); +} +.plan-your-day-category-delete-cell { + width: 1%; + white-space: nowrap; +} + +.plan-your-day-add-category-action { + margin: 1rem 0 0.65rem; +} + +.plan-your-day-add-category-button { + min-height: 2.5rem; + padding-right: 1rem; padding-left: 1rem; + font-weight: 700; +} + +.plan-your-day-categories-field { + display: block; +} + +.plan-your-day-categories-field > th { + display: none; +} + +.plan-your-day-categories-field > td { + display: block; + width: 100%; + box-sizing: border-box; + padding-left: 0; } .plan-your-day-admin-back-to-top { diff --git a/plugin/plan-your-day/assets/css/plan.css b/plugin/plan-your-day/assets/css/plan.css index 3d6872c..baab8fe 100644 --- a/plugin/plan-your-day/assets/css/plan.css +++ b/plugin/plan-your-day/assets/css/plan.css @@ -1,3 +1,27 @@ +@font-face { + font-family: "Noto Sans"; + src: url("../fonts/noto-sans-v42-latin-ext-300.woff2") format("woff2"); + font-style: normal; + font-weight: 300; + font-display: swap; +} + +@font-face { + font-family: "Noto Sans"; + src: url("../fonts/noto-sans-v42-latin-ext-regular.woff2") format("woff2"); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: "Noto Sans"; + src: url("../fonts/noto-sans-v42-latin-ext-700.woff2") format("woff2"); + font-style: normal; + font-weight: 700; + font-display: swap; +} + .plan-your-day, .plan-your-day *, .plan-your-day *::before, @@ -8,18 +32,197 @@ .plan-your-day { --focus-ring-color: #1168d8; --focus-ring-offset-color: #ffffff; + --pyd-font-family: "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --pyd-text: #173038; + --pyd-heading: #102027; + --pyd-hero-heading: #112127; + --pyd-muted: #35545d; + --pyd-accent: #8a4f0f; + --pyd-accent-soft: rgba(138, 79, 15, 0.2); + --pyd-accent-border: rgba(138, 79, 15, 0.42); + --pyd-border-subtle: rgba(23, 48, 56, 0.08); + --pyd-border: rgba(23, 48, 56, 0.12); + --pyd-border-medium: rgba(23, 48, 56, 0.14); + --pyd-border-strong: rgba(23, 48, 56, 0.16); + --pyd-border-emphasis: rgba(23, 48, 56, 0.22); + --pyd-control-mark-border: rgba(23, 48, 56, 0.35); + --pyd-surface-bg: radial-gradient(circle at top right, rgba(241, 181, 67, 0.18), transparent 26%), + linear-gradient(180deg, #fffdf8 0%, #f4ecdb 100%); + --pyd-surface-shadow: 0 0.85rem 2rem rgba(23, 48, 56, 0.12); + --pyd-card-bg: rgba(255, 255, 255, 0.82); + --pyd-card-shadow: 0 0.65rem 1.45rem rgba(23, 48, 56, 0.06); + --pyd-panel-bg: #fffefb; + --pyd-option-bg: #fffdf9; + --pyd-option-selected-bg: linear-gradient(180deg, #fffaf0 0%, #f7ecd6 100%); + --pyd-option-selected-shadow: 0 0.85rem 1.8rem rgba(138, 79, 15, 0.12); + --pyd-radio-bg: #ffffff; + --pyd-radio-checked-bg: radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%), + #fff7e9; + --pyd-chip-bg: rgba(23, 48, 56, 0.08); + --pyd-hero-note-bg: rgba(255, 255, 255, 0.72); + --pyd-primary-bg: #173038; + --pyd-primary-hover-bg: #0f2228; + --pyd-primary-text: #fffdf7; + --pyd-primary-soft-text: #fffaf0; + --pyd-secondary-hover-bg: #f6eddb; + --pyd-interactive-hover-bg: rgba(255, 255, 255, 0.38); + --pyd-item-bg: linear-gradient(180deg, #fffdf9 0%, #f7f0e0 100%); + --pyd-item-hover-shadow: 0 0.85rem 1.8rem rgba(23, 48, 56, 0.08); + --pyd-item-drag-shadow: 0 1.1rem 2rem rgba(23, 48, 56, 0.14); + --pyd-feedback-border: rgba(255, 255, 255, 0.55); + --pyd-feedback-shadow: 0 0.5rem 1rem rgba(23, 48, 56, 0.18); + --pyd-disabled-bg: rgba(255, 255, 255, 0.65); + --pyd-disabled-text: #5c737a; + --pyd-empty-border: rgba(23, 48, 56, 0.26); + --pyd-empty-bg: rgba(255, 255, 255, 0.62); + --pyd-summary-bg: linear-gradient(180deg, #fffdf7 0%, #f8f1e0 100%); + --pyd-summary-tile-bg: rgba(255, 255, 255, 0.7); + --pyd-note-border: rgba(23, 48, 56, 0.12); + --pyd-note-bg: rgba(223, 236, 241, 0.75); + --pyd-note-text: #163038; + --pyd-warning-border: rgba(138, 79, 15, 0.2); + --pyd-warning-bg: rgba(255, 243, 225, 0.9); + --pyd-warning-text: #6e410d; + --pyd-success: #167c3b; + --pyd-danger: #a73525; + --pyd-map-bg: #f3ede0; + --pyd-scrollbar-track: rgba(23, 48, 56, 0.08); + --pyd-scrollbar-thumb: rgba(138, 79, 15, 0.55); + --pyd-scrollbar-thumb-border: rgba(255, 255, 255, 0.65); + color-scheme: light; font-family: - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; + var(--pyd-font-family); line-height: 1.5; padding:0; } -.plan-your-day img, -.plan-your-day svg { +.plan-your-day[data-plan-color-mode="dark"] { + --focus-ring-color: #9fc5ff; + --focus-ring-offset-color: #232323; + --pyd-text: #f3f1ea; + --pyd-heading: #fffaf0; + --pyd-hero-heading: #fffdf7; + --pyd-muted: #c5d0d1; + --pyd-accent: #f1b543; + --pyd-accent-soft: rgba(241, 181, 67, 0.24); + --pyd-accent-border: rgba(241, 181, 67, 0.55); + --pyd-border-subtle: rgba(255, 255, 255, 0.08); + --pyd-border: rgba(255, 255, 255, 0.12); + --pyd-border-medium: rgba(255, 255, 255, 0.14); + --pyd-border-strong: rgba(255, 255, 255, 0.18); + --pyd-border-emphasis: rgba(255, 255, 255, 0.26); + --pyd-control-mark-border: rgba(255, 255, 255, 0.42); + --pyd-surface-bg: radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%), + linear-gradient(180deg, #2e2e2e 0%, #232323 100%); + --pyd-surface-shadow: 0 0.85rem 2rem rgba(0, 0, 0, 0.34); + --pyd-card-bg: rgba(48, 48, 48, 0.92); + --pyd-card-shadow: 0 0.65rem 1.45rem rgba(0, 0, 0, 0.24); + --pyd-panel-bg: #2b2b2b; + --pyd-option-bg: #292929; + --pyd-option-selected-bg: linear-gradient(180deg, #3a3427 0%, #2b2925 100%); + --pyd-option-selected-shadow: 0 0.85rem 1.8rem rgba(0, 0, 0, 0.28); + --pyd-radio-bg: #232323; + --pyd-radio-checked-bg: radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%), + #302818; + --pyd-chip-bg: rgba(255, 255, 255, 0.1); + --pyd-hero-note-bg: rgba(255, 255, 255, 0.08); + --pyd-primary-bg: #f1b543; + --pyd-primary-hover-bg: #ffd36b; + --pyd-primary-text: #232323; + --pyd-primary-soft-text: #232323; + --pyd-secondary-hover-bg: rgba(241, 181, 67, 0.16); + --pyd-interactive-hover-bg: rgba(255, 255, 255, 0.08); + --pyd-item-bg: linear-gradient(180deg, #303030 0%, #282828 100%); + --pyd-item-hover-shadow: 0 0.85rem 1.8rem rgba(0, 0, 0, 0.26); + --pyd-item-drag-shadow: 0 1.1rem 2rem rgba(0, 0, 0, 0.36); + --pyd-feedback-border: rgba(241, 181, 67, 0.36); + --pyd-feedback-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.28); + --pyd-disabled-bg: rgba(255, 255, 255, 0.08); + --pyd-disabled-text: #9aa6a8; + --pyd-empty-border: rgba(255, 255, 255, 0.24); + --pyd-empty-bg: rgba(255, 255, 255, 0.07); + --pyd-summary-bg: linear-gradient(180deg, #303030 0%, #292929 100%); + --pyd-summary-tile-bg: rgba(255, 255, 255, 0.08); + --pyd-note-border: rgba(145, 198, 217, 0.28); + --pyd-note-bg: rgba(62, 91, 101, 0.5); + --pyd-note-text: #e9f6f9; + --pyd-warning-border: rgba(241, 181, 67, 0.34); + --pyd-warning-bg: rgba(78, 57, 24, 0.68); + --pyd-warning-text: #ffe1a3; + --pyd-success: #78d690; + --pyd-danger: #ff9a8a; + --pyd-map-bg: #303030; + --pyd-scrollbar-track: rgba(255, 255, 255, 0.08); + --pyd-scrollbar-thumb: rgba(241, 181, 67, 0.62); + --pyd-scrollbar-thumb-border: rgba(35, 35, 35, 0.9); + color-scheme: dark; +} + +@media (prefers-color-scheme: dark) { + .plan-your-day[data-plan-color-mode-default="system"]:not([data-plan-color-mode]) { + --focus-ring-color: #9fc5ff; + --focus-ring-offset-color: #232323; + --pyd-text: #f3f1ea; + --pyd-heading: #fffaf0; + --pyd-hero-heading: #fffdf7; + --pyd-muted: #c5d0d1; + --pyd-accent: #f1b543; + --pyd-accent-soft: rgba(241, 181, 67, 0.24); + --pyd-accent-border: rgba(241, 181, 67, 0.55); + --pyd-border-subtle: rgba(255, 255, 255, 0.08); + --pyd-border: rgba(255, 255, 255, 0.12); + --pyd-border-medium: rgba(255, 255, 255, 0.14); + --pyd-border-strong: rgba(255, 255, 255, 0.18); + --pyd-border-emphasis: rgba(255, 255, 255, 0.26); + --pyd-control-mark-border: rgba(255, 255, 255, 0.42); + --pyd-surface-bg: radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%), + linear-gradient(180deg, #2e2e2e 0%, #232323 100%); + --pyd-surface-shadow: 0 0.85rem 2rem rgba(0, 0, 0, 0.34); + --pyd-card-bg: rgba(48, 48, 48, 0.92); + --pyd-card-shadow: 0 0.65rem 1.45rem rgba(0, 0, 0, 0.24); + --pyd-panel-bg: #2b2b2b; + --pyd-option-bg: #292929; + --pyd-option-selected-bg: linear-gradient(180deg, #3a3427 0%, #2b2925 100%); + --pyd-option-selected-shadow: 0 0.85rem 1.8rem rgba(0, 0, 0, 0.28); + --pyd-radio-bg: #232323; + --pyd-radio-checked-bg: radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%), + #302818; + --pyd-chip-bg: rgba(255, 255, 255, 0.1); + --pyd-hero-note-bg: rgba(255, 255, 255, 0.08); + --pyd-primary-bg: #f1b543; + --pyd-primary-hover-bg: #ffd36b; + --pyd-primary-text: #232323; + --pyd-primary-soft-text: #232323; + --pyd-secondary-hover-bg: rgba(241, 181, 67, 0.16); + --pyd-interactive-hover-bg: rgba(255, 255, 255, 0.08); + --pyd-item-bg: linear-gradient(180deg, #303030 0%, #282828 100%); + --pyd-item-hover-shadow: 0 0.85rem 1.8rem rgba(0, 0, 0, 0.26); + --pyd-item-drag-shadow: 0 1.1rem 2rem rgba(0, 0, 0, 0.36); + --pyd-feedback-border: rgba(241, 181, 67, 0.36); + --pyd-feedback-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.28); + --pyd-disabled-bg: rgba(255, 255, 255, 0.08); + --pyd-disabled-text: #9aa6a8; + --pyd-empty-border: rgba(255, 255, 255, 0.24); + --pyd-empty-bg: rgba(255, 255, 255, 0.07); + --pyd-summary-bg: linear-gradient(180deg, #303030 0%, #292929 100%); + --pyd-summary-tile-bg: rgba(255, 255, 255, 0.08); + --pyd-note-border: rgba(145, 198, 217, 0.28); + --pyd-note-bg: rgba(62, 91, 101, 0.5); + --pyd-note-text: #e9f6f9; + --pyd-warning-border: rgba(241, 181, 67, 0.34); + --pyd-warning-bg: rgba(78, 57, 24, 0.68); + --pyd-warning-text: #ffe1a3; + --pyd-success: #78d690; + --pyd-danger: #ff9a8a; + --pyd-map-bg: #303030; + --pyd-scrollbar-track: rgba(255, 255, 255, 0.08); + --pyd-scrollbar-thumb: rgba(241, 181, 67, 0.62); + --pyd-scrollbar-thumb-border: rgba(35, 35, 35, 0.9); + color-scheme: dark; + } +} + +.plan-your-day img { display: block; max-width: 100%; } @@ -55,7 +258,8 @@ .plan-your-day { width: min(100% - 2rem, 1180px); margin: clamp(1rem, 4vw, 3rem) auto; - color: #173038; + color: var(--pyd-text); + container-type: inline-size; } .plan-your-day [hidden] { @@ -64,12 +268,10 @@ .plan-your-day__surface { padding: clamp(1rem, 1.6vw, 1.5rem); - border: 1px solid rgba(23, 48, 56, 0.12); + border: thin solid var(--pyd-border); border-radius: 1.5rem; - background: - radial-gradient(circle at top right, rgba(241, 181, 67, 0.18), transparent 26%), - linear-gradient(180deg, #fffdf8 0%, #f4ecdb 100%); - box-shadow: 0 0.85rem 2rem rgba(23, 48, 56, 0.12); + background: var(--pyd-surface-bg); + box-shadow: var(--pyd-surface-shadow); } .plan-your-day__hero { @@ -77,7 +279,7 @@ gap: 0.8rem; margin-bottom: 1.2rem; padding-bottom: 1rem; - border-bottom: 1px solid rgba(23, 48, 56, 0.12); + border-bottom: thin solid var(--pyd-border); } .plan-your-day__eyebrow, @@ -88,13 +290,14 @@ font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - color: #8a4f0f; + color: var(--pyd-accent); } .plan-your-day__hero h2 { font-size: clamp(1.8rem, 3.3vw, 2.45rem); line-height: 1; - color: #112127; + color: var(--pyd-hero-heading); + font-weight: 700; } .plan-your-day__intro, @@ -114,15 +317,15 @@ margin: 0; font-size: 0.95rem; line-height: 1.5; - color: #35545d; + color: var(--pyd-muted); } .plan-your-day__hero-note { max-width: 33rem; padding: 0.75rem 0.9rem; border-radius: 1rem; - background: rgba(255, 255, 255, 0.72); - border: 1px solid rgba(23, 48, 56, 0.08); + background: var(--pyd-hero-note-bg); + border: thin solid var(--pyd-border-subtle); } .plan-your-day__layout { @@ -135,14 +338,16 @@ .plan-your-day__preview-panel { display: grid; gap: 0.85rem; + min-width: 0; } .plan-your-day__card { + min-width: 0; padding: 1rem; - border: 1px solid rgba(23, 48, 56, 0.12); + border: thin solid var(--pyd-border); border-radius: 1.2rem; - background: rgba(255, 255, 255, 0.82); - box-shadow: 0 0.65rem 1.45rem rgba(23, 48, 56, 0.06); + background: var(--pyd-card-bg); + box-shadow: var(--pyd-card-shadow); } .plan-your-day__card-header, @@ -164,7 +369,9 @@ .plan-your-day__card h3, .plan-your-day__card h4 { - color: #102027; + color: var(--pyd-heading); + font-weight: 700; + margin-top: 0; } .plan-your-day__card h3 { @@ -186,8 +393,8 @@ min-height: 1.8rem; padding: 0.3rem 0.7rem; border-radius: 999px; - background: #173038; - color: #fffaf0; + background: var(--pyd-primary-bg); + color: var(--pyd-primary-soft-text); font-size: 0.85rem; font-weight: 700; white-space: nowrap; @@ -221,9 +428,9 @@ gap: 0.35rem; min-height: 100%; padding: 0.85rem 0.9rem 0.85rem 2.7rem; - border: 1px solid rgba(23, 48, 56, 0.16); + border: thin solid var(--pyd-border-strong); border-radius: 1rem; - background: #fffdf9; + background: var(--pyd-option-bg); position: relative; transition: border-color 0.2s ease, @@ -240,31 +447,38 @@ left: 0.9rem; width: 1.05rem; height: 1.05rem; - border: 2px solid rgba(23, 48, 56, 0.35); + border: 2px solid var(--pyd-control-mark-border); border-radius: 999px; - background: #fff; + background: var(--pyd-radio-bg); +} + +.plan-your-day__start-option:hover .plan-your-day__start-option-body, +.plan-your-day__start-option:active .plan-your-day__start-option-body { + border-color: var(--pyd-accent-border); + background: var(--pyd-secondary-hover-bg); + box-shadow: var(--pyd-item-hover-shadow); + transform: translateY(-1px); } .plan-your-day__category-option input:checked + .plan-your-day__category-option-body, .plan-your-day__start-option input:checked + .plan-your-day__start-option-body { - border-color: rgba(138, 79, 15, 0.45); - background: linear-gradient(180deg, #fffaf0 0%, #f7ecd6 100%); - box-shadow: 0 0.85rem 1.8rem rgba(138, 79, 15, 0.12); + border-color: var(--pyd-accent-border); + background: var(--pyd-option-selected-bg); + box-shadow: var(--pyd-option-selected-shadow); transform: translateY(-1px); } .plan-your-day__category-option input:checked + .plan-your-day__category-option-body::before, .plan-your-day__start-option input:checked + .plan-your-day__start-option-body::before { - border-color: #8a4f0f; - background: - radial-gradient(circle at center, #8a4f0f 0 45%, transparent 48% 100%), - #fff7e9; + border-color: var(--pyd-accent); + background: var(--pyd-radio-checked-bg); } .plan-your-day__category-radio:focus-visible + .plan-your-day__category-option-body, .plan-your-day__category-trigger:focus-visible, .plan-your-day__category-search input:focus-visible, .plan-your-day__category-search-button:focus-visible, +.plan-your-day__color-mode-toggle:focus-visible, .plan-your-day__load-more-button:focus-visible, .plan-your-day__start-option input:focus-visible + .plan-your-day__start-option-body, .plan-your-day__start-toggle:focus-visible, @@ -285,7 +499,7 @@ .plan-your-day__category-title, .plan-your-day__start-title { display: block; - color: #102027; + color: var(--pyd-heading); font-size: 1rem; font-weight: 700; } @@ -308,10 +522,10 @@ width: 100%; min-height: 2.85rem; padding: 0.75rem 0.9rem; - border: 1px solid rgba(23, 48, 56, 0.16); + border: thin solid var(--pyd-border-strong); border-radius: 0.5rem; - background: #fffefb; - color: #102027; + background: var(--pyd-panel-bg); + color: var(--pyd-heading); } .plan-your-day__category-search-button { @@ -323,9 +537,9 @@ } .plan-your-day__category-accordion-item { - border: 1px solid rgba(23, 48, 56, 0.14); + border: thin solid var(--pyd-border-medium); border-radius: 1rem; - background: linear-gradient(180deg, #fffefb 0%, #f6efde 100%); + background: var(--pyd-item-bg); transition: border-color 0.18s ease, box-shadow 0.18s ease, @@ -333,8 +547,11 @@ } .plan-your-day__category-accordion-item.is-expanded { - border-color: rgba(138, 79, 15, 0.32); - box-shadow: 0 0.85rem 1.7rem rgba(23, 48, 56, 0.08); + border-color: var(--pyd-accent-border); + box-shadow: var(--pyd-item-hover-shadow); +} +.plan-your-day__category-accordion-item.is-expanded > h4 button { + box-shadow: 0 4px 5px -5px #333333d7; } .plan-your-day__category-accordion-heading { @@ -364,7 +581,7 @@ .plan-your-day__category-trigger:hover, .plan-your-day__category-trigger:active { transform: translateY(-1px); - background: rgba(255, 255, 255, 0.38); + background: var(--pyd-interactive-hover-bg); } .plan-your-day__category-trigger-copy { @@ -381,7 +598,7 @@ flex-shrink: 0; } -.plan-your-day__category-trigger-icon { +.plan-your-day__category-trigger-chevron { width: 0.72rem; height: 0.72rem; border-right: 2px solid currentColor; @@ -390,12 +607,12 @@ transition: transform 0.18s ease; } -.plan-your-day__category-trigger[aria-expanded="true"] .plan-your-day__category-trigger-icon { +.plan-your-day__category-trigger[aria-expanded="true"] .plan-your-day__category-trigger-chevron { transform: rotate(-135deg); } .plan-your-day__category-panel { - padding: 0 1rem 1rem; + padding:.66rem 1rem 1rem; } .plan-your-day__custom-search-header { @@ -404,6 +621,7 @@ .plan-your-day__custom-search-header h4 { margin: 0; + font-weight: 700; } .plan-your-day__category-results-scroll { @@ -411,7 +629,7 @@ overflow-y: auto; padding-right: 0.35rem; scrollbar-width: thin; - scrollbar-color: rgba(138, 79, 15, 0.55) rgba(23, 48, 56, 0.08); + scrollbar-color: var(--pyd-scrollbar-thumb) var(--pyd-scrollbar-track); transition: opacity 0.18s ease, filter 0.18s ease; } @@ -420,14 +638,14 @@ } .plan-your-day__category-results-scroll::-webkit-scrollbar-track { - background: rgba(23, 48, 56, 0.08); + background: var(--pyd-scrollbar-track); border-radius: 999px; } .plan-your-day__category-results-scroll::-webkit-scrollbar-thumb { - background: rgba(138, 79, 15, 0.55); + background: var(--pyd-scrollbar-thumb); border-radius: 999px; - border: 2px solid rgba(255, 255, 255, 0.65); + border: 2px solid var(--pyd-scrollbar-thumb-border); } .plan-your-day__start-toggle { @@ -438,10 +656,10 @@ gap: 0.55rem; min-height: 2.5rem; padding: 0.55rem 0.9rem; - border: 1px solid rgba(23, 48, 56, 0.14); + border: thin solid var(--pyd-border-medium); border-radius: 999px; - background: #fffefb; - color: #173038; + background: var(--pyd-panel-bg); + color: var(--pyd-text); font-size: 0.88rem; font-weight: 700; line-height: 1; @@ -456,11 +674,75 @@ .plan-your-day__start-toggle:hover, .plan-your-day__start-toggle:active { transform: translateY(-1px); - border-color: rgba(138, 79, 15, 0.42); - background: #f6eddb; + border-color: var(--pyd-accent-border); + background: var(--pyd-secondary-hover-bg); +} + +.plan-your-day__hero-tools { + display: flex; + align-items: center; + justify-content: flex-start; } -.plan-your-day__start-toggle-icon { +.plan-your-day__color-mode-toggle { + appearance: none; + display: inline-flex; + position: relative; + align-items: center; + gap: 0.55rem; + min-height: 2.5rem; + padding: 0.45rem 0.75rem 0.45rem 0.5rem; + border: thin solid var(--pyd-border-medium); + border-radius: 999px; + background: var(--pyd-panel-bg); + color: var(--pyd-text); + cursor: pointer; + font-family: inherit; + font-size: 0.88rem; + font-weight: 700; + line-height: 1; + transition: + transform 0.18s ease, + border-color 0.18s ease, + background-color 0.18s ease, + color 0.18s ease; +} + +.plan-your-day__color-mode-toggle::before { + content: ""; + width: 2.2rem; + height: 1.25rem; + border-radius: 999px; + background: var(--pyd-chip-bg); + box-shadow: inset 0 0 0 thin var(--pyd-border); +} + +.plan-your-day__color-mode-toggle::after { + content: ""; + position: absolute; + width: 0.85rem; + height: 0.85rem; + margin-left: 0.2rem; + border-radius: 999px; + background: var(--pyd-primary-bg); + transform: translateX(0); + transition: + background-color 0.18s ease, + transform 0.18s ease; +} + +.plan-your-day__color-mode-toggle[aria-pressed="true"]::after { + transform: translateX(0.95rem); +} + +.plan-your-day__color-mode-toggle:hover, +.plan-your-day__color-mode-toggle:active { + transform: translateY(-1px); + border-color: var(--pyd-accent-border); + background: var(--pyd-secondary-hover-bg); +} + +.plan-your-day__start-toggle-chevron { width: 0.6rem; height: 0.6rem; border-right: 2px solid currentColor; @@ -469,7 +751,7 @@ transition: transform 0.18s ease; } -.plan-your-day__start-toggle.is-collapsed .plan-your-day__start-toggle-icon { +.plan-your-day__start-toggle.is-collapsed .plan-your-day__start-toggle-chevron { transform: rotate(45deg); } @@ -495,7 +777,7 @@ margin: 0; font-size: 0.88rem; font-weight: 700; - color: #8a4f0f; + color: var(--pyd-accent); } .plan-your-day__results-list, @@ -518,9 +800,9 @@ gap: 0.8rem; position: relative; padding: 0.85rem; - border: 1px solid rgba(23, 48, 56, 0.14); + border: thin solid var(--pyd-border-medium); border-radius: .5rem; - background: linear-gradient(180deg, #fffdf9 0%, #f7f0e0 100%); + background: var(--pyd-item-bg); transition: transform 0.18s ease, box-shadow 0.18s ease, @@ -531,11 +813,14 @@ .plan-your-day__trip-copy { display: grid; gap: 0.15rem; + min-width: 0; } .plan-your-day__result-copy h4, .plan-your-day__trip-copy h4 { font-size: 1rem; + margin: 0; + font-weight: 700; } .plan-your-day__result-tools, @@ -563,7 +848,7 @@ min-height: 2.625rem; height: 42px; padding: 0.65rem 0.95rem; - border: 1px solid transparent; + border: thin solid transparent; border-radius: 999px; appearance: none; cursor: pointer; @@ -585,9 +870,9 @@ .plan-your-day__trip-tools a, .plan-your-day__trip-tools button, .plan-your-day__maps-link { - border-color: rgba(23, 48, 56, 0.16); - background: #fffefb; - color: #173038; + border-color: var(--pyd-border-strong); + background: var(--pyd-panel-bg); + color: var(--pyd-text); } .plan-your-day__result-link:hover, @@ -601,16 +886,16 @@ .plan-your-day__maps-link:hover:not(.is-disabled), .plan-your-day__maps-link:active:not(.is-disabled) { transform: translateY(-1px); - border-color: rgba(138, 79, 15, 0.42); - background: #f6eddb; + border-color: var(--pyd-accent-border); + background: var(--pyd-secondary-hover-bg); } .plan-your-day__result-add, .plan-your-day__submit, .plan-your-day__load-more-button, .plan-your-day__category-search-button { - background: #173038; - color: #fffdf7; + background: var(--pyd-primary-bg); + color: var(--pyd-primary-text); } .plan-your-day__result-add:hover, @@ -622,7 +907,7 @@ .plan-your-day__submit:hover, .plan-your-day__submit:active { transform: translateY(-1px); - background: #0f2228; + background: var(--pyd-primary-hover-bg); } .plan-your-day__load-more-button { @@ -643,8 +928,8 @@ min-height: 2.625rem; padding: 0.65rem 0.95rem; border-radius: 999px; - background: rgba(23, 48, 56, 0.08); - color: #173038; + background: var(--pyd-chip-bg); + color: var(--pyd-text); font-size: 0.9rem; font-weight: 700; } @@ -669,11 +954,11 @@ z-index: 2; max-width: calc(100% - 1.3rem); padding: 0.45rem 0.7rem; - border: 1px solid rgba(255, 255, 255, 0.55); + border: thin solid var(--pyd-feedback-border); border-radius: 0.5rem; - background: #173038; - box-shadow: 0 0.5rem 1rem rgba(23, 48, 56, 0.18); - color: #fffdf7; + background: var(--pyd-primary-bg); + box-shadow: var(--pyd-feedback-shadow); + color: var(--pyd-primary-text); font-size: 0.85rem; font-weight: 700; line-height: 1.2; @@ -681,22 +966,22 @@ } .plan-your-day.is-enhanced .plan-your-day__trip-item:hover { - border-color: rgba(138, 79, 15, 0.4); - box-shadow: 0 0.85rem 1.8rem rgba(23, 48, 56, 0.08); + border-color: var(--pyd-accent-border); + box-shadow: var(--pyd-item-hover-shadow); } .plan-your-day__trip-item.is-dragging { opacity: 0.7; transform: scale(0.985); - box-shadow: 0 1.1rem 2rem rgba(23, 48, 56, 0.14); + box-shadow: var(--pyd-item-drag-shadow); } .plan-your-day__trip-item.is-drop-before { - box-shadow: inset 0 4px 0 #8a4f0f; + box-shadow: inset 0 4px 0 var(--pyd-accent); } .plan-your-day__trip-item.is-drop-after { - box-shadow: inset 0 -4px 0 #8a4f0f; + box-shadow: inset 0 -4px 0 var(--pyd-accent); } .plan-your-day__trip-main { @@ -712,8 +997,8 @@ width: 2.1rem; height: 2.1rem; border-radius: 999px; - background: #173038; - color: #fffdf7; + background: var(--pyd-primary-bg); + color: var(--pyd-primary-text); font-size: 0.95rem; font-weight: 700; } @@ -725,8 +1010,8 @@ min-width: 4rem; padding: 0.6rem 16px .4rem 32px; border-radius: 1000px; - background: #173038; - color: #fffef9; + background: var(--pyd-primary-bg); + color: var(--pyd-primary-text); font-size: 0.75rem; font-weight: 700; text-transform: uppercase; @@ -737,9 +1022,12 @@ .plan-your-day__drag-handle:before { content: ''; display: inline-block; - aspect-ratio: 94/150; - background: url("../icons/grip-vertical-solid.svg") center no-repeat; - background-size: 10px; + width: 10px; + height: 18px; + background-image: + repeating-radial-gradient(circle, currentColor 0 2px, transparent 2.5px 100%); + background-size: 5px 6px; + background-position: center; position: absolute; left: 8px; top:5px; @@ -759,9 +1047,9 @@ .plan-your-day__reorder-disabled { opacity: 0.45; cursor: not-allowed; - border-color: rgba(23, 48, 56, 0.12); - background: rgba(255, 255, 255, 0.65); - color: #5c737a; + border-color: var(--pyd-border); + background: var(--pyd-disabled-bg); + color: var(--pyd-disabled-text); } .plan-your-day__results-empty, @@ -771,15 +1059,16 @@ gap: 0.55rem; place-items: start; padding: 1.1rem; - border: 1px dashed rgba(23, 48, 56, 0.26); + border: thin dashed var(--pyd-empty-border); border-radius: .5rem; - background: rgba(255, 255, 255, 0.62); + background: var(--pyd-empty-bg); } .plan-your-day__results-empty h4, .plan-your-day__trip-empty h4, .plan-your-day__preview-empty h4 { font-size: 1.15rem; + font-weight: 700; } .plan-your-day__custom-start { @@ -790,17 +1079,94 @@ display: inline-block; margin-bottom: 0.45rem; font-weight: 700; - color: #102027; + color: var(--pyd-heading); +} + +.plan-your-day__custom-start-field { + position: relative; } .plan-your-day__custom-start input { width: 100%; min-height: 2.85rem; - padding: 0.75rem 0.9rem; - border: 1px solid rgba(23, 48, 56, 0.16); + padding: 0.75rem 2.85rem 0.75rem 0.9rem; + border: thin solid var(--pyd-border-strong); border-radius: 0.5rem; - background: #fffefb; - color: #102027; + background: var(--pyd-panel-bg); + color: var(--pyd-heading); +} + +.plan-your-day__custom-start-indicator { + position: absolute; + top: 50%; + right: 0.9rem; + display: none; + width: 1rem; + height: 1rem; + pointer-events: none; + transform: translateY(-50%); +} + +.plan-your-day__custom-start[data-plan-custom-start-state="checking"] .plan-your-day__custom-start-indicator, +.plan-your-day__custom-start[data-plan-custom-start-state="found"] .plan-your-day__custom-start-indicator, +.plan-your-day__custom-start[data-plan-custom-start-state="not_found"] .plan-your-day__custom-start-indicator { + display: block; +} + +.plan-your-day__custom-start[data-plan-custom-start-state="checking"] .plan-your-day__custom-start-indicator { + border: 2px solid var(--pyd-border-emphasis); + border-top-color: var(--pyd-accent); + border-radius: 999px; + animation: plan-your-day-spin 0.8s linear infinite; +} + +.plan-your-day__custom-start[data-plan-custom-start-state="found"] .plan-your-day__custom-start-indicator::before { + content: ""; + position: absolute; + top: 0.05rem; + left: 0.3rem; + width: 0.38rem; + height: 0.72rem; + border-right: 0.18rem solid var(--pyd-success); + border-bottom: 0.18rem solid var(--pyd-success); + transform: rotate(45deg); +} + +.plan-your-day__custom-start[data-plan-custom-start-state="not_found"] .plan-your-day__custom-start-indicator { + border: 2px solid var(--pyd-danger); + border-radius: 999px; +} + +.plan-your-day__custom-start[data-plan-custom-start-state="not_found"] .plan-your-day__custom-start-indicator::before, +.plan-your-day__custom-start[data-plan-custom-start-state="not_found"] .plan-your-day__custom-start-indicator::after { + content: ""; + position: absolute; + top: 0.2rem; + left: 0.4rem; + width: 0.15rem; + height: 0.52rem; + border-radius: 999px; + background: var(--pyd-danger); +} + +.plan-your-day__custom-start[data-plan-custom-start-state="not_found"] .plan-your-day__custom-start-indicator::before { + transform: rotate(45deg); +} + +.plan-your-day__custom-start[data-plan-custom-start-state="not_found"] .plan-your-day__custom-start-indicator::after { + transform: rotate(-45deg); +} + +@keyframes plan-your-day-spin { + to { + transform: translateY(-50%) rotate(360deg); + } +} + +@media (prefers-reduced-motion: reduce) { + .plan-your-day__custom-start[data-plan-custom-start-state="checking"] .plan-your-day__custom-start-indicator { + animation: none; + } } .plan-your-day__input-help, @@ -817,16 +1183,16 @@ } .plan-your-day__clear-link { - border-color: rgba(23, 48, 56, 0.12); + border-color: var(--pyd-border); background: transparent; - color: #173038; + color: var(--pyd-text); } .plan-your-day__clear-link:hover, .plan-your-day__clear-link:active { transform: translateY(-1px); - border-color: rgba(23, 48, 56, 0.22); - background: rgba(255, 255, 255, 0.7); + border-color: var(--pyd-border-emphasis); + background: var(--pyd-summary-tile-bg); } .plan-your-day__maps-link.is-disabled, @@ -845,21 +1211,21 @@ .plan-your-day__message { padding: 0.75rem 0.85rem; border-radius: .5rem; - border: 1px solid transparent; + border: thin solid transparent; font-size: 0.92rem; line-height: 1.45; } .plan-your-day__message--note { - border-color: rgba(23, 48, 56, 0.12); - background: rgba(223, 236, 241, 0.75); - color: #163038; + border-color: var(--pyd-note-border); + background: var(--pyd-note-bg); + color: var(--pyd-note-text); } .plan-your-day__message--warning { - border-color: rgba(138, 79, 15, 0.2); - background: rgba(255, 243, 225, 0.9); - color: #6e410d; + border-color: var(--pyd-warning-border); + background: var(--pyd-warning-bg); + color: var(--pyd-warning-text); } .plan-your-day__summary { @@ -867,8 +1233,8 @@ margin-bottom: 0; padding: 0.9rem; border-radius: .5rem; - background: linear-gradient(180deg, #fffdf7 0%, #f8f1e0 100%); - border: 1px solid rgba(23, 48, 56, 0.1); + background: var(--pyd-summary-bg); + border: thin solid var(--pyd-border-subtle); } .plan-your-day__summary-handoff { @@ -884,7 +1250,7 @@ font-weight: 700; letter-spacing: 0; text-transform: uppercase; - color: #8a4f0f; + color: var(--pyd-accent); } .plan-your-day__maps-link--summary { @@ -897,7 +1263,7 @@ .plan-your-day__summary-grid div { padding: 0.7rem 0.8rem; border-radius: 0.5rem; - background: rgba(255, 255, 255, 0.7); + background: var(--pyd-summary-tile-bg); } .plan-your-day__summary-grid dt { @@ -906,20 +1272,20 @@ font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - color: #8a4f0f; + color: var(--pyd-accent); } .plan-your-day__summary-grid dd { margin: 0; font-size: 0.95rem; - color: #102027; + color: var(--pyd-heading); } .plan-your-day__map-wrap { overflow: hidden; border-radius: 1rem; - border: 1px solid rgba(23, 48, 56, 0.12); - background: #f3ede0; + border: thin solid var(--pyd-border); + background: var(--pyd-map-bg); } .plan-your-day__map-frame { @@ -927,7 +1293,7 @@ width: 100%; min-height: 24rem; border: 0; - background: #f3ede0; + background: var(--pyd-map-bg); } @media (min-width: 980px) { @@ -936,15 +1302,6 @@ align-items: end; } - .plan-your-day__layout { - grid-template-columns: minmax(18rem, 30rem) minmax(0, 1fr); - } - - .plan-your-day__preview-card { - position: sticky; - top: 7.75rem; - } - .plan-your-day__summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -960,6 +1317,17 @@ } } +@container (min-width: 60rem) { + .plan-your-day__layout { + grid-template-columns: minmax(18rem, 30rem) minmax(0, 1fr); + } + + .plan-your-day__preview-card { + position: sticky; + top: 7.75rem; + } +} + @media (max-width: 979px) { .plan-your-day__card-header, .plan-your-day__summary-top { diff --git a/plugin/plan-your-day/assets/css/plan.min.css b/plugin/plan-your-day/assets/css/plan.min.css index eed3a01..22e80b7 100644 --- a/plugin/plan-your-day/assets/css/plan.min.css +++ b/plugin/plan-your-day/assets/css/plan.min.css @@ -1 +1 @@ -.plan-your-day,.plan-your-day *,.plan-your-day *::before,.plan-your-day *::after{box-sizing:border-box;}.plan-your-day{--focus-ring-color:#1168d8;--focus-ring-offset-color:#ffffff;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.5;padding:0;}.plan-your-day img,.plan-your-day svg{display:block;max-width:100%;}.plan-your-day button,.plan-your-day input,.plan-your-day select,.plan-your-day textarea{touch-action:manipulation;}.plan-your-day button{cursor:pointer;}.plan-your-day a{color:inherit;}.plan-your-day .screen-reader-text,.plan-your-day .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}.plan-your-day{width:min(100% - 2rem,1180px);margin:clamp(1rem,4vw,3rem) auto;color:#173038;}.plan-your-day [hidden]{display:none!important;}.plan-your-day__surface{padding:clamp(1rem,1.6vw,1.5rem);border:1px solid rgba(23,48,56,.12);border-radius:1.5rem;background:radial-gradient(circle at top right,rgba(241,181,67,.18),transparent 26%),linear-gradient(180deg,#fffdf8 0%,#f4ecdb 100%);box-shadow:0 .85rem 2rem rgba(23,48,56,.12);}.plan-your-day__hero{display:grid;gap:.8rem;margin-bottom:1.2rem;padding-bottom:1rem;border-bottom:1px solid rgba(23,48,56,.12);}.plan-your-day__eyebrow,.plan-your-day__summary-eyebrow,.plan-your-day__trip-kicker{margin:0;font-size:.78rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#8a4f0f;}.plan-your-day__hero h2{font-size:clamp(1.8rem,3.3vw,2.45rem);line-height:1;color:#112127;}.plan-your-day__intro,.plan-your-day__hero-note,.plan-your-day__card-header p,.plan-your-day__category-description,.plan-your-day__result-meta,.plan-your-day__trip-meta,.plan-your-day__start-description,.plan-your-day__summary-overview,.plan-your-day__preview-empty p,.plan-your-day__results-empty p,.plan-your-day__trip-empty p,.plan-your-day__input-help,.plan-your-day__auto-note,.plan-your-day__noscript-note{margin:0;font-size:.95rem;line-height:1.5;color:#35545d;}.plan-your-day__hero-note{max-width:33rem;padding:.75rem .9rem;border-radius:1rem;background:rgba(255,255,255,.72);border:1px solid rgba(23,48,56,.08);}.plan-your-day__layout{display:grid;gap:1.2rem;align-items:start;}.plan-your-day__controls,.plan-your-day__preview-panel{display:grid;gap:.85rem;}.plan-your-day__card{padding:1rem;border:1px solid rgba(23,48,56,.12);border-radius:1.2rem;background:rgba(255,255,255,.82);box-shadow:0 .65rem 1.45rem rgba(23,48,56,.06);}.plan-your-day__card-header,.plan-your-day__summary-top{display:flex;align-items:start;justify-content:space-between;gap:1rem;margin-bottom:.8rem;}.plan-your-day__trip-header-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:.6rem;flex-wrap:wrap;}.plan-your-day__card h3,.plan-your-day__card h4{color:#102027;}.plan-your-day__card h3{font-size:1.2rem;margin-bottom:.25rem;}.plan-your-day__fieldset{border:0;padding:0;margin:0;}.plan-your-day__count-pill,.plan-your-day__summary-count{display:inline-flex;align-items:center;justify-content:center;min-height:1.8rem;padding:.3rem .7rem;border-radius:999px;background:#173038;color:#fffaf0;font-size:.85rem;font-weight:700;white-space:nowrap;}.plan-your-day__category-grid,.plan-your-day__start-options,.plan-your-day__results-list,.plan-your-day__trip-list,.plan-your-day__summary-grid{display:grid;gap:.7rem;}.plan-your-day__category-option,.plan-your-day__start-option{display:block;cursor:pointer;}.plan-your-day__category-option input,.plan-your-day__start-option input{position:absolute;opacity:0;pointer-events:none;}.plan-your-day__category-option-body,.plan-your-day__start-option-body{display:grid;gap:.35rem;min-height:100%;padding:.85rem .9rem .85rem 2.7rem;border:1px solid rgba(23,48,56,.16);border-radius:1rem;background:#fffdf9;position:relative;transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease,background-color .2s ease;}.plan-your-day__category-option-body::before,.plan-your-day__start-option-body::before{content:"";position:absolute;top:.92rem;left:.9rem;width:1.05rem;height:1.05rem;border:2px solid rgba(23,48,56,.35);border-radius:999px;background:#fff;}.plan-your-day__category-option input:checked + .plan-your-day__category-option-body,.plan-your-day__start-option input:checked + .plan-your-day__start-option-body{border-color:rgba(138,79,15,.45);background:linear-gradient(180deg,#fffaf0 0%,#f7ecd6 100%);box-shadow:0 .85rem 1.8rem rgba(138,79,15,.12);transform:translateY(-1px);}.plan-your-day__category-option input:checked + .plan-your-day__category-option-body::before,.plan-your-day__start-option input:checked + .plan-your-day__start-option-body::before{border-color:#8a4f0f;background:radial-gradient(circle at center,#8a4f0f 0 45%,transparent 48% 100%),#fff7e9;}.plan-your-day__category-radio:focus-visible + .plan-your-day__category-option-body,.plan-your-day__category-trigger:focus-visible,.plan-your-day__category-search input:focus-visible,.plan-your-day__category-search-button:focus-visible,.plan-your-day__load-more-button:focus-visible,.plan-your-day__start-option input:focus-visible + .plan-your-day__start-option-body,.plan-your-day__start-toggle:focus-visible,.plan-your-day__custom-start input:focus-visible,.plan-your-day__submit:focus-visible,.plan-your-day__load-more-button:focus-visible,.plan-your-day__maps-link:focus-visible,.plan-your-day__clear-link:focus-visible,.plan-your-day__result-add:focus-visible,.plan-your-day__result-link:focus-visible,.plan-your-day__trip-tools a:focus-visible,.plan-your-day__trip-tools button:focus-visible{outline:3px solid var(--focus-ring-color);outline-offset:3px;box-shadow:0 0 0 3px var(--focus-ring-offset-color);}.plan-your-day__category-title,.plan-your-day__start-title{display:block;color:#102027;font-size:1rem;font-weight:700;}.plan-your-day__category-accordion{display:grid;gap:.7rem;}.plan-your-day__category-search{margin-bottom:.85rem;}.plan-your-day__category-search-controls{display:grid;gap:.65rem;}.plan-your-day__category-search input{width:100%;min-height:2.85rem;padding:.75rem .9rem;border:1px solid rgba(23,48,56,.16);border-radius:.5rem;background:#fffefb;color:#102027;}.plan-your-day__category-search-button{justify-self:start;}.plan-your-day__custom-search-results{margin-bottom:.85rem;}.plan-your-day__category-accordion-item{border:1px solid rgba(23,48,56,.14);border-radius:1rem;background:linear-gradient(180deg,#fffefb 0%,#f6efde 100%);transition:border-color .18s ease,box-shadow .18s ease,background-color .18s ease;}.plan-your-day__category-accordion-item.is-expanded{border-color:rgba(138,79,15,.32);box-shadow:0 .85rem 1.7rem rgba(23,48,56,.08);}.plan-your-day__category-accordion-heading{margin:0;}.plan-your-day__category-trigger{appearance:none;display:flex;align-items:center;justify-content:space-between;gap:1rem;width:100%;padding:.95rem 1rem;border:0;border-radius:1rem;background:transparent;color:inherit;text-align:left;cursor:pointer;transition:transform .18s ease,background-color .18s ease,color .18s ease;}.plan-your-day__category-trigger:hover,.plan-your-day__category-trigger:active{transform:translateY(-1px);background:rgba(255,255,255,.38);}.plan-your-day__category-trigger-copy{display:grid;gap:.18rem;min-width:0;flex:1 1 auto;}.plan-your-day__category-trigger-side{display:inline-flex;align-items:center;gap:.7rem;flex-shrink:0;}.plan-your-day__category-trigger-icon{width:.72rem;height:.72rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .18s ease;}.plan-your-day__category-trigger[aria-expanded="true"] .plan-your-day__category-trigger-icon{transform:rotate(-135deg);}.plan-your-day__category-panel{padding:0 1rem 1rem;}.plan-your-day__custom-search-header{margin:0;}.plan-your-day__custom-search-header h4{margin:0;}.plan-your-day__category-results-scroll{max-height:min(28rem,65vh);overflow-y:auto;padding-right:.35rem;scrollbar-width:thin;scrollbar-color:rgba(138,79,15,.55) rgba(23,48,56,.08);transition:opacity .18s ease,filter .18s ease;}.plan-your-day__category-results-scroll::-webkit-scrollbar{width:.65rem;}.plan-your-day__category-results-scroll::-webkit-scrollbar-track{background:rgba(23,48,56,.08);border-radius:999px;}.plan-your-day__category-results-scroll::-webkit-scrollbar-thumb{background:rgba(138,79,15,.55);border-radius:999px;border:2px solid rgba(255,255,255,.65);}.plan-your-day__start-toggle{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.55rem;min-height:2.5rem;padding:.55rem .9rem;border:1px solid rgba(23,48,56,.14);border-radius:999px;background:#fffefb;color:#173038;font-size:.88rem;font-weight:700;line-height:1;cursor:pointer;transition:transform .18s ease,border-color .18s ease,background-color .18s ease,color .18s ease;}.plan-your-day__start-toggle:hover,.plan-your-day__start-toggle:active{transform:translateY(-1px);border-color:rgba(138,79,15,.42);background:#f6eddb;}.plan-your-day__start-toggle-icon{width:.6rem;height:.6rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-135deg);transition:transform .18s ease;}.plan-your-day__start-toggle.is-collapsed .plan-your-day__start-toggle-icon{transform:rotate(45deg);}.plan-your-day__start-panel{will-change:height;}.plan-your-day__category-results-scroll[aria-busy="true"],.plan-your-day [data-plan-trip-region][aria-busy="true"],.plan-your-day [data-plan-preview-card][aria-busy="true"] .plan-your-day__map-wrap,.plan-your-day [data-plan-preview-card][aria-busy="true"] .plan-your-day__summary{opacity:.72;filter:saturate(.88);}.plan-your-day__category-results-scroll[aria-busy="true"],.plan-your-day [data-plan-trip-region][aria-busy="true"],.plan-your-day [data-plan-preview-card][aria-busy="true"]{cursor:progress;}.plan-your-day__result-distance{margin:0;font-size:.88rem;font-weight:700;color:#8a4f0f;}.plan-your-day__results-list,.plan-your-day__trip-list{margin:0;padding:0;list-style:none;}.plan-your-day__load-more{display:flex;justify-content:center;margin-top:.85rem;}.plan-your-day__result-item,.plan-your-day__trip-item{display:flex;flex-wrap:wrap;gap:.8rem;position:relative;padding:.85rem;border:1px solid rgba(23,48,56,.14);border-radius:.5rem;background:linear-gradient(180deg,#fffdf9 0%,#f7f0e0 100%);transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;}.plan-your-day__result-copy,.plan-your-day__trip-copy{display:grid;gap:.15rem;}.plan-your-day__result-copy h4,.plan-your-day__trip-copy h4{font-size:1rem;}.plan-your-day__result-tools,.plan-your-day__trip-tools{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;}.plan-your-day__result-link,.plan-your-day__result-add,.plan-your-day__load-more-button,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button,.plan-your-day__reorder-disabled,.plan-your-day__submit,.plan-your-day__maps-link,.plan-your-day__clear-link,.plan-your-day__load-more-button,.plan-your-day__category-search-button{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;height:42px;padding:.65rem .95rem;border:1px solid transparent;border-radius:999px;appearance:none;cursor:pointer;font-family:inherit;font-size:.9rem;font-weight:700;line-height:1;text-decoration:none;transition:transform .18s ease,background-color .18s ease,color .18s ease,border-color .18s ease,box-shadow .18s ease;}.plan-your-day__result-link,.plan-your-day__load-more-button,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button,.plan-your-day__maps-link{border-color:rgba(23,48,56,.16);background:#fffefb;color:#173038;}.plan-your-day__result-link:hover,.plan-your-day__result-link:active,.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__trip-tools a:hover,.plan-your-day__trip-tools a:active,.plan-your-day__trip-tools button:hover:not(:disabled),.plan-your-day__trip-tools button:active:not(:disabled),.plan-your-day__maps-link:hover:not(.is-disabled),.plan-your-day__maps-link:active:not(.is-disabled){transform:translateY(-1px);border-color:rgba(138,79,15,.42);background:#f6eddb;}.plan-your-day__result-add,.plan-your-day__submit,.plan-your-day__load-more-button,.plan-your-day__category-search-button{background:#173038;color:#fffdf7;}.plan-your-day__result-add:hover,.plan-your-day__result-add:active,.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__category-search-button:hover,.plan-your-day__category-search-button:active,.plan-your-day__submit:hover,.plan-your-day__submit:active{transform:translateY(-1px);background:#0f2228;}.plan-your-day__load-more-button{min-width:11rem;}.plan-your-day__load-more-button.is-loading,.plan-your-day__load-more-button:disabled{cursor:progress;opacity:.82;transform:none;}.plan-your-day__result-added{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;padding:.65rem .95rem;border-radius:999px;background:rgba(23,48,56,.08);color:#173038;font-size:.9rem;font-weight:700;}.plan-your-day__load-more{display:flex;justify-content:flex-start;margin-top:.9rem;}.plan-your-day__load-more-button:disabled,.plan-your-day__load-more-button.is-loading{opacity:.72;cursor:progress;transform:none;}.plan-your-day__waypoint-feedback{position:absolute;top:.65rem;right:.65rem;z-index:2;max-width:calc(100% - 1.3rem);padding:.45rem .7rem;border:1px solid rgba(255,255,255,.55);border-radius:.5rem;background:#173038;box-shadow:0 .5rem 1rem rgba(23,48,56,.18);color:#fffdf7;font-size:.85rem;font-weight:700;line-height:1.2;pointer-events:none;}.plan-your-day.is-enhanced .plan-your-day__trip-item:hover{border-color:rgba(138,79,15,.4);box-shadow:0 .85rem 1.8rem rgba(23,48,56,.08);}.plan-your-day__trip-item.is-dragging{opacity:.7;transform:scale(.985);box-shadow:0 1.1rem 2rem rgba(23,48,56,.14);}.plan-your-day__trip-item.is-drop-before{box-shadow:inset 0 4px 0 #8a4f0f;}.plan-your-day__trip-item.is-drop-after{box-shadow:inset 0 -4px 0 #8a4f0f;}.plan-your-day__trip-main{display:grid;grid-template-columns:auto 1fr;gap:.75rem;}.plan-your-day__trip-number{display:inline-flex;align-items:center;justify-content:center;width:2.1rem;height:2.1rem;border-radius:999px;background:#173038;color:#fffdf7;font-size:.95rem;font-weight:700;}.plan-your-day__drag-handle{display:none;align-items:center;justify-content:center;min-width:4rem;padding:.6rem 16px .4rem 32px;border-radius:1000px;background:#173038;color:#fffef9;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;position:relative;height:42px;}.plan-your-day__drag-handle:before{content:'';display:inline-block;aspect-ratio:94/150;background:url("../icons/grip-vertical-solid.svg") center no-repeat;background-size:10px;position:absolute;left:8px;top:5px;bottom:5px;margin:auto 0;}.plan-your-day.is-enhanced .plan-your-day__drag-handle{display:inline-flex;}.plan-your-day__reorder-links{display:inline-flex;gap:.4rem;flex-wrap:wrap;}.plan-your-day__reorder-disabled{opacity:.45;cursor:not-allowed;border-color:rgba(23,48,56,.12);background:rgba(255,255,255,.65);color:#5c737a;}.plan-your-day__results-empty,.plan-your-day__trip-empty,.plan-your-day__preview-empty{display:grid;gap:.55rem;place-items:start;padding:1.1rem;border:1px dashed rgba(23,48,56,.26);border-radius:.5rem;background:rgba(255,255,255,.62);}.plan-your-day__results-empty h4,.plan-your-day__trip-empty h4,.plan-your-day__preview-empty h4{font-size:1.15rem;}.plan-your-day__custom-start{margin-top:.85rem;}.plan-your-day__custom-start label{display:inline-block;margin-bottom:.45rem;font-weight:700;color:#102027;}.plan-your-day__custom-start input{width:100%;min-height:2.85rem;padding:.75rem .9rem;border:1px solid rgba(23,48,56,.16);border-radius:.5rem;background:#fffefb;color:#102027;}.plan-your-day__input-help,.plan-your-day__auto-note,.plan-your-day__noscript-note{margin-top:.55rem;}.plan-your-day__actions{display:flex;gap:.75rem;flex-wrap:wrap;margin-top:.85rem;}.plan-your-day__clear-link{border-color:rgba(23,48,56,.12);background:transparent;color:#173038;}.plan-your-day__clear-link:hover,.plan-your-day__clear-link:active{transform:translateY(-1px);border-color:rgba(23,48,56,.22);background:rgba(255,255,255,.7);}.plan-your-day__maps-link.is-disabled,.plan-your-day__maps-link[aria-disabled="true"]{opacity:.5;cursor:not-allowed;pointer-events:none;}.plan-your-day__messages{display:grid;gap:.7rem;margin-bottom:1rem;}.plan-your-day__message{padding:.75rem .85rem;border-radius:.5rem;border:1px solid transparent;font-size:.92rem;line-height:1.45;}.plan-your-day__message--note{border-color:rgba(23,48,56,.12);background:rgba(223,236,241,.75);color:#163038;}.plan-your-day__message--warning{border-color:rgba(138,79,15,.2);background:rgba(255,243,225,.9);color:#6e410d;}.plan-your-day__summary{margin-top:1rem;margin-bottom:0;padding:.9rem;border-radius:.5rem;background:linear-gradient(180deg,#fffdf7 0%,#f8f1e0 100%);border:1px solid rgba(23,48,56,.1);}.plan-your-day__summary-handoff{display:flex;flex-direction:column;gap:.45rem;margin-top:1rem;}.plan-your-day__summary-handoff-label{margin:0;font-size:.84rem;font-weight:700;letter-spacing:0;text-transform:uppercase;color:#8a4f0f;}.plan-your-day__maps-link--summary{width:100%;min-height:3.35rem;padding:.9rem 1.2rem;font-size:1.15rem;}.plan-your-day__summary-grid div{padding:.7rem .8rem;border-radius:.5rem;background:rgba(255,255,255,.7);}.plan-your-day__summary-grid dt{margin-bottom:.35rem;font-size:.84rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#8a4f0f;}.plan-your-day__summary-grid dd{margin:0;font-size:.95rem;color:#102027;}.plan-your-day__map-wrap{overflow:hidden;border-radius:1rem;border:1px solid rgba(23,48,56,.12);background:#f3ede0;}.plan-your-day__map-frame{display:block;width:100%;min-height:24rem;border:0;background:#f3ede0;}@media (min-width:980px){.plan-your-day__hero{grid-template-columns:minmax(0,1fr) auto;align-items:end}.plan-your-day__layout{grid-template-columns:minmax(18rem,30rem) minmax(0,1fr)}.plan-your-day__preview-card{position:sticky;top:7.75rem}.plan-your-day__summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.plan-your-day__result-item,.plan-your-day__trip-item{grid-template-columns:minmax(0,1fr) auto}.plan-your-day__trip-tools{align-self:center;justify-content:end}}@media (max-width:979px){.plan-your-day__card-header,.plan-your-day__summary-top{flex-direction:column;align-items:start}}@media (max-width:639px){.plan-your-day__surface,.plan-your-day__card{border-radius:1rem}.plan-your-day__surface{padding:1rem}.plan-your-day__category-option-body,.plan-your-day__start-option-body{padding-left:2.75rem}.plan-your-day__category-trigger{align-items:start}.plan-your-day__category-trigger-side{gap:.55rem}.plan-your-day__category-results-scroll{max-height:min(22rem,55vh)}.plan-your-day__actions,.plan-your-day__result-tools,.plan-your-day__trip-tools,.plan-your-day__reorder-links{width:100%}.plan-your-day__result-link,.plan-your-day__result-add,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button,.plan-your-day__reorder-disabled,.plan-your-day__submit,.plan-your-day__maps-link,.plan-your-day__clear-link,.plan-your-day__category-search-button{width:100%}.plan-your-day__summary-grid{grid-template-columns:1fr}.plan-your-day__map-frame{min-height:20rem}}@media (min-width:768px){.plan-your-day__category-description{display:block}.plan-your-day__category-search-controls{grid-template-columns:minmax(0,1fr) auto;align-items:center}}@media (max-width:767px){.plan-your-day__category-description{display:none}} \ No newline at end of file +@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-300.woff2") format("woff2");font-style:normal;font-weight:300;font-display:swap}@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-regular.woff2") format("woff2");font-style:normal;font-weight:400;font-display:swap}@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-700.woff2") format("woff2");font-style:normal;font-weight:700;font-display:swap}.plan-your-day,.plan-your-day *,.plan-your-day ::after,.plan-your-day ::before{box-sizing:border-box}.plan-your-day{--focus-ring-color:#1168d8;--focus-ring-offset-color:#ffffff;--pyd-font-family:"Noto Sans",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;--pyd-text:#173038;--pyd-heading:#102027;--pyd-hero-heading:#112127;--pyd-muted:#35545d;--pyd-accent:#8a4f0f;--pyd-accent-soft:rgba(138, 79, 15, 0.2);--pyd-accent-border:rgba(138, 79, 15, 0.42);--pyd-border-subtle:rgba(23, 48, 56, 0.08);--pyd-border:rgba(23, 48, 56, 0.12);--pyd-border-medium:rgba(23, 48, 56, 0.14);--pyd-border-strong:rgba(23, 48, 56, 0.16);--pyd-border-emphasis:rgba(23, 48, 56, 0.22);--pyd-control-mark-border:rgba(23, 48, 56, 0.35);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.18), transparent 26%),linear-gradient(180deg, #fffdf8 0%, #f4ecdb 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(23, 48, 56, 0.12);--pyd-card-bg:rgba(255, 255, 255, 0.82);--pyd-card-shadow:0 0.65rem 1.45rem rgba(23, 48, 56, 0.06);--pyd-panel-bg:#fffefb;--pyd-option-bg:#fffdf9;--pyd-option-selected-bg:linear-gradient(180deg, #fffaf0 0%, #f7ecd6 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(138, 79, 15, 0.12);--pyd-radio-bg:#ffffff;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#fff7e9;--pyd-chip-bg:rgba(23, 48, 56, 0.08);--pyd-hero-note-bg:rgba(255, 255, 255, 0.72);--pyd-primary-bg:#173038;--pyd-primary-hover-bg:#0f2228;--pyd-primary-text:#fffdf7;--pyd-primary-soft-text:#fffaf0;--pyd-secondary-hover-bg:#f6eddb;--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.38);--pyd-item-bg:linear-gradient(180deg, #fffdf9 0%, #f7f0e0 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(23, 48, 56, 0.08);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(23, 48, 56, 0.14);--pyd-feedback-border:rgba(255, 255, 255, 0.55);--pyd-feedback-shadow:0 0.5rem 1rem rgba(23, 48, 56, 0.18);--pyd-disabled-bg:rgba(255, 255, 255, 0.65);--pyd-disabled-text:#5c737a;--pyd-empty-border:rgba(23, 48, 56, 0.26);--pyd-empty-bg:rgba(255, 255, 255, 0.62);--pyd-summary-bg:linear-gradient(180deg, #fffdf7 0%, #f8f1e0 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.7);--pyd-note-border:rgba(23, 48, 56, 0.12);--pyd-note-bg:rgba(223, 236, 241, 0.75);--pyd-note-text:#163038;--pyd-warning-border:rgba(138, 79, 15, 0.2);--pyd-warning-bg:rgba(255, 243, 225, 0.9);--pyd-warning-text:#6e410d;--pyd-success:#167c3b;--pyd-danger:#a73525;--pyd-map-bg:#f3ede0;--pyd-scrollbar-track:rgba(23, 48, 56, 0.08);--pyd-scrollbar-thumb:rgba(138, 79, 15, 0.55);--pyd-scrollbar-thumb-border:rgba(255, 255, 255, 0.65);color-scheme:light;font-family:var(--pyd-font-family);line-height:1.5;padding:0}.plan-your-day[data-plan-color-mode=dark]{--focus-ring-color:#9fc5ff;--focus-ring-offset-color:#232323;--pyd-text:#f3f1ea;--pyd-heading:#fffaf0;--pyd-hero-heading:#fffdf7;--pyd-muted:#c5d0d1;--pyd-accent:#f1b543;--pyd-accent-soft:rgba(241, 181, 67, 0.24);--pyd-accent-border:rgba(241, 181, 67, 0.55);--pyd-border-subtle:rgba(255, 255, 255, 0.08);--pyd-border:rgba(255, 255, 255, 0.12);--pyd-border-medium:rgba(255, 255, 255, 0.14);--pyd-border-strong:rgba(255, 255, 255, 0.18);--pyd-border-emphasis:rgba(255, 255, 255, 0.26);--pyd-control-mark-border:rgba(255, 255, 255, 0.42);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%),linear-gradient(180deg, #2e2e2e 0%, #232323 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(0, 0, 0, 0.34);--pyd-card-bg:rgba(48, 48, 48, 0.92);--pyd-card-shadow:0 0.65rem 1.45rem rgba(0, 0, 0, 0.24);--pyd-panel-bg:#2b2b2b;--pyd-option-bg:#292929;--pyd-option-selected-bg:linear-gradient(180deg, #3a3427 0%, #2b2925 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.28);--pyd-radio-bg:#232323;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#302818;--pyd-chip-bg:rgba(255, 255, 255, 0.1);--pyd-hero-note-bg:rgba(255, 255, 255, 0.08);--pyd-primary-bg:#f1b543;--pyd-primary-hover-bg:#ffd36b;--pyd-primary-text:#232323;--pyd-primary-soft-text:#232323;--pyd-secondary-hover-bg:rgba(241, 181, 67, 0.16);--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.08);--pyd-item-bg:linear-gradient(180deg, #303030 0%, #282828 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.26);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(0, 0, 0, 0.36);--pyd-feedback-border:rgba(241, 181, 67, 0.36);--pyd-feedback-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.28);--pyd-disabled-bg:rgba(255, 255, 255, 0.08);--pyd-disabled-text:#9aa6a8;--pyd-empty-border:rgba(255, 255, 255, 0.24);--pyd-empty-bg:rgba(255, 255, 255, 0.07);--pyd-summary-bg:linear-gradient(180deg, #303030 0%, #292929 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.08);--pyd-note-border:rgba(145, 198, 217, 0.28);--pyd-note-bg:rgba(62, 91, 101, 0.5);--pyd-note-text:#e9f6f9;--pyd-warning-border:rgba(241, 181, 67, 0.34);--pyd-warning-bg:rgba(78, 57, 24, 0.68);--pyd-warning-text:#ffe1a3;--pyd-success:#78d690;--pyd-danger:#ff9a8a;--pyd-map-bg:#303030;--pyd-scrollbar-track:rgba(255, 255, 255, 0.08);--pyd-scrollbar-thumb:rgba(241, 181, 67, 0.62);--pyd-scrollbar-thumb-border:rgba(35, 35, 35, 0.9);color-scheme:dark}@media (prefers-color-scheme:dark){.plan-your-day[data-plan-color-mode-default=system]:not([data-plan-color-mode]){--focus-ring-color:#9fc5ff;--focus-ring-offset-color:#232323;--pyd-text:#f3f1ea;--pyd-heading:#fffaf0;--pyd-hero-heading:#fffdf7;--pyd-muted:#c5d0d1;--pyd-accent:#f1b543;--pyd-accent-soft:rgba(241, 181, 67, 0.24);--pyd-accent-border:rgba(241, 181, 67, 0.55);--pyd-border-subtle:rgba(255, 255, 255, 0.08);--pyd-border:rgba(255, 255, 255, 0.12);--pyd-border-medium:rgba(255, 255, 255, 0.14);--pyd-border-strong:rgba(255, 255, 255, 0.18);--pyd-border-emphasis:rgba(255, 255, 255, 0.26);--pyd-control-mark-border:rgba(255, 255, 255, 0.42);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%),linear-gradient(180deg, #2e2e2e 0%, #232323 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(0, 0, 0, 0.34);--pyd-card-bg:rgba(48, 48, 48, 0.92);--pyd-card-shadow:0 0.65rem 1.45rem rgba(0, 0, 0, 0.24);--pyd-panel-bg:#2b2b2b;--pyd-option-bg:#292929;--pyd-option-selected-bg:linear-gradient(180deg, #3a3427 0%, #2b2925 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.28);--pyd-radio-bg:#232323;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#302818;--pyd-chip-bg:rgba(255, 255, 255, 0.1);--pyd-hero-note-bg:rgba(255, 255, 255, 0.08);--pyd-primary-bg:#f1b543;--pyd-primary-hover-bg:#ffd36b;--pyd-primary-text:#232323;--pyd-primary-soft-text:#232323;--pyd-secondary-hover-bg:rgba(241, 181, 67, 0.16);--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.08);--pyd-item-bg:linear-gradient(180deg, #303030 0%, #282828 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.26);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(0, 0, 0, 0.36);--pyd-feedback-border:rgba(241, 181, 67, 0.36);--pyd-feedback-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.28);--pyd-disabled-bg:rgba(255, 255, 255, 0.08);--pyd-disabled-text:#9aa6a8;--pyd-empty-border:rgba(255, 255, 255, 0.24);--pyd-empty-bg:rgba(255, 255, 255, 0.07);--pyd-summary-bg:linear-gradient(180deg, #303030 0%, #292929 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.08);--pyd-note-border:rgba(145, 198, 217, 0.28);--pyd-note-bg:rgba(62, 91, 101, 0.5);--pyd-note-text:#e9f6f9;--pyd-warning-border:rgba(241, 181, 67, 0.34);--pyd-warning-bg:rgba(78, 57, 24, 0.68);--pyd-warning-text:#ffe1a3;--pyd-success:#78d690;--pyd-danger:#ff9a8a;--pyd-map-bg:#303030;--pyd-scrollbar-track:rgba(255, 255, 255, 0.08);--pyd-scrollbar-thumb:rgba(241, 181, 67, 0.62);--pyd-scrollbar-thumb-border:rgba(35, 35, 35, 0.9);color-scheme:dark}}.plan-your-day img{display:block;max-width:100%}.plan-your-day button,.plan-your-day input,.plan-your-day select,.plan-your-day textarea{touch-action:manipulation}.plan-your-day button{cursor:pointer}.plan-your-day a{color:inherit}.plan-your-day .screen-reader-text,.plan-your-day .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.plan-your-day{width:min(100% - 2rem,1180px);margin:clamp(1rem,4vw,3rem) auto;color:var(--pyd-text);container-type:inline-size}.plan-your-day [hidden]{display:none!important}.plan-your-day__surface{padding:clamp(1rem,1.6vw,1.5rem);border:thin solid var(--pyd-border);border-radius:1.5rem;background:var(--pyd-surface-bg);box-shadow:var(--pyd-surface-shadow)}.plan-your-day__hero{display:grid;gap:.8rem;margin-bottom:1.2rem;padding-bottom:1rem;border-bottom:thin solid var(--pyd-border)}.plan-your-day__eyebrow,.plan-your-day__summary-eyebrow,.plan-your-day__trip-kicker{margin:0;font-size:.78rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__hero h2{font-size:clamp(1.8rem, 3.3vw, 2.45rem);line-height:1;color:var(--pyd-hero-heading);font-weight:700}.plan-your-day__auto-note,.plan-your-day__card-header p,.plan-your-day__category-description,.plan-your-day__hero-note,.plan-your-day__input-help,.plan-your-day__intro,.plan-your-day__noscript-note,.plan-your-day__preview-empty p,.plan-your-day__result-meta,.plan-your-day__results-empty p,.plan-your-day__start-description,.plan-your-day__summary-overview,.plan-your-day__trip-empty p,.plan-your-day__trip-meta{margin:0;font-size:.95rem;line-height:1.5;color:var(--pyd-muted)}.plan-your-day__hero-note{max-width:33rem;padding:.75rem .9rem;border-radius:1rem;background:var(--pyd-hero-note-bg);border:thin solid var(--pyd-border-subtle)}.plan-your-day__layout{display:grid;gap:1.2rem;align-items:start}.plan-your-day__controls,.plan-your-day__preview-panel{display:grid;gap:.85rem;min-width:0}.plan-your-day__card{min-width:0;padding:1rem;border:thin solid var(--pyd-border);border-radius:1.2rem;background:var(--pyd-card-bg);box-shadow:var(--pyd-card-shadow)}.plan-your-day__card-header,.plan-your-day__summary-top{display:flex;align-items:start;justify-content:space-between;gap:1rem;margin-bottom:.8rem}.plan-your-day__trip-header-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:.6rem;flex-wrap:wrap}.plan-your-day__card h3,.plan-your-day__card h4{color:var(--pyd-heading);font-weight:700;margin-top:0}.plan-your-day__card h3{font-size:1.2rem;margin-bottom:.25rem}.plan-your-day__fieldset{border:0;padding:0;margin:0}.plan-your-day__count-pill,.plan-your-day__summary-count{display:inline-flex;align-items:center;justify-content:center;min-height:1.8rem;padding:.3rem .7rem;border-radius:999px;background:var(--pyd-primary-bg);color:var(--pyd-primary-soft-text);font-size:.85rem;font-weight:700;white-space:nowrap}.plan-your-day__category-grid,.plan-your-day__results-list,.plan-your-day__start-options,.plan-your-day__summary-grid,.plan-your-day__trip-list{display:grid;gap:.7rem}.plan-your-day__category-option,.plan-your-day__start-option{display:block;cursor:pointer}.plan-your-day__category-option input,.plan-your-day__start-option input{position:absolute;opacity:0;pointer-events:none}.plan-your-day__category-option-body,.plan-your-day__start-option-body{display:grid;gap:.35rem;min-height:100%;padding:.85rem .9rem .85rem 2.7rem;border:thin solid var(--pyd-border-strong);border-radius:1rem;background:var(--pyd-option-bg);position:relative;transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease,background-color .2s ease}.plan-your-day__category-option-body::before,.plan-your-day__start-option-body::before{content:"";position:absolute;top:.92rem;left:.9rem;width:1.05rem;height:1.05rem;border:2px solid var(--pyd-control-mark-border);border-radius:999px;background:var(--pyd-radio-bg)}.plan-your-day__start-option:active .plan-your-day__start-option-body,.plan-your-day__start-option:hover .plan-your-day__start-option-body{border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg);box-shadow:var(--pyd-item-hover-shadow);transform:translateY(-1px)}.plan-your-day__category-option input:checked+.plan-your-day__category-option-body,.plan-your-day__start-option input:checked+.plan-your-day__start-option-body{border-color:var(--pyd-accent-border);background:var(--pyd-option-selected-bg);box-shadow:var(--pyd-option-selected-shadow);transform:translateY(-1px)}.plan-your-day__category-option input:checked+.plan-your-day__category-option-body::before,.plan-your-day__start-option input:checked+.plan-your-day__start-option-body::before{border-color:var(--pyd-accent);background:var(--pyd-radio-checked-bg)}.plan-your-day__category-radio:focus-visible+.plan-your-day__category-option-body,.plan-your-day__category-search input:focus-visible,.plan-your-day__category-search-button:focus-visible,.plan-your-day__category-trigger:focus-visible,.plan-your-day__clear-link:focus-visible,.plan-your-day__color-mode-toggle:focus-visible,.plan-your-day__custom-start input:focus-visible,.plan-your-day__load-more-button:focus-visible,.plan-your-day__maps-link:focus-visible,.plan-your-day__result-add:focus-visible,.plan-your-day__result-link:focus-visible,.plan-your-day__start-option input:focus-visible+.plan-your-day__start-option-body,.plan-your-day__start-toggle:focus-visible,.plan-your-day__submit:focus-visible,.plan-your-day__trip-tools a:focus-visible,.plan-your-day__trip-tools button:focus-visible{outline:3px solid var(--focus-ring-color);outline-offset:3px;box-shadow:0 0 0 3px var(--focus-ring-offset-color)}.plan-your-day__category-title,.plan-your-day__start-title{display:block;color:var(--pyd-heading);font-size:1rem;font-weight:700}.plan-your-day__category-accordion{display:grid;gap:.7rem}.plan-your-day__category-search{margin-bottom:.85rem}.plan-your-day__category-search-controls{display:grid;gap:.65rem}.plan-your-day__category-search input{width:100%;min-height:2.85rem;padding:.75rem .9rem;border:thin solid var(--pyd-border-strong);border-radius:.5rem;background:var(--pyd-panel-bg);color:var(--pyd-heading)}.plan-your-day__category-search-button{justify-self:start}.plan-your-day__custom-search-results{margin-bottom:.85rem}.plan-your-day__category-accordion-item{border:thin solid var(--pyd-border-medium);border-radius:1rem;background:var(--pyd-item-bg);transition:border-color .18s ease,box-shadow .18s ease,background-color .18s ease}.plan-your-day__category-accordion-item.is-expanded{border-color:var(--pyd-accent-border);box-shadow:var(--pyd-item-hover-shadow)}.plan-your-day__category-accordion-item.is-expanded>h4 button{box-shadow:0 4px 5px -5px #333333d7}.plan-your-day__category-accordion-heading{margin:0}.plan-your-day__category-trigger{appearance:none;display:flex;align-items:center;justify-content:space-between;gap:1rem;width:100%;padding:.95rem 1rem;border:0;border-radius:1rem;background:0 0;color:inherit;text-align:left;cursor:pointer;transition:transform .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__category-trigger:active,.plan-your-day__category-trigger:hover{transform:translateY(-1px);background:var(--pyd-interactive-hover-bg)}.plan-your-day__category-trigger-copy{display:grid;gap:.18rem;min-width:0;flex:1 1 auto}.plan-your-day__category-trigger-side{display:inline-flex;align-items:center;gap:.7rem;flex-shrink:0}.plan-your-day__category-trigger-chevron{width:.72rem;height:.72rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .18s ease}.plan-your-day__category-trigger[aria-expanded=true] .plan-your-day__category-trigger-chevron{transform:rotate(-135deg)}.plan-your-day__category-panel{padding:.66rem 1rem 1rem}.plan-your-day__custom-search-header{margin:0}.plan-your-day__custom-search-header h4{margin:0;font-weight:700}.plan-your-day__category-results-scroll{max-height:min(28rem,65vh);overflow-y:auto;padding-right:.35rem;scrollbar-width:thin;scrollbar-color:var(--pyd-scrollbar-thumb) var(--pyd-scrollbar-track);transition:opacity .18s ease,filter .18s ease}.plan-your-day__category-results-scroll::-webkit-scrollbar{width:.65rem}.plan-your-day__category-results-scroll::-webkit-scrollbar-track{background:var(--pyd-scrollbar-track);border-radius:999px}.plan-your-day__category-results-scroll::-webkit-scrollbar-thumb{background:var(--pyd-scrollbar-thumb);border-radius:999px;border:2px solid var(--pyd-scrollbar-thumb-border)}.plan-your-day__start-toggle{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.55rem;min-height:2.5rem;padding:.55rem .9rem;border:thin solid var(--pyd-border-medium);border-radius:999px;background:var(--pyd-panel-bg);color:var(--pyd-text);font-size:.88rem;font-weight:700;line-height:1;cursor:pointer;transition:transform .18s ease,border-color .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__start-toggle:active,.plan-your-day__start-toggle:hover{transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__hero-tools{display:flex;align-items:center;justify-content:flex-start}.plan-your-day__color-mode-toggle{appearance:none;display:inline-flex;position:relative;align-items:center;gap:.55rem;min-height:2.5rem;padding:.45rem .75rem .45rem .5rem;border:thin solid var(--pyd-border-medium);border-radius:999px;background:var(--pyd-panel-bg);color:var(--pyd-text);cursor:pointer;font-family:inherit;font-size:.88rem;font-weight:700;line-height:1;transition:transform .18s ease,border-color .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__color-mode-toggle::before{content:"";width:2.2rem;height:1.25rem;border-radius:999px;background:var(--pyd-chip-bg);box-shadow:inset 0 0 0 thin var(--pyd-border)}.plan-your-day__color-mode-toggle::after{content:"";position:absolute;width:.85rem;height:.85rem;margin-left:.2rem;border-radius:999px;background:var(--pyd-primary-bg);transform:translateX(0);transition:background-color .18s ease,transform .18s ease}.plan-your-day__color-mode-toggle[aria-pressed=true]::after{transform:translateX(.95rem)}.plan-your-day__color-mode-toggle:active,.plan-your-day__color-mode-toggle:hover{transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__start-toggle-chevron{width:.6rem;height:.6rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-135deg);transition:transform .18s ease}.plan-your-day__start-toggle.is-collapsed .plan-your-day__start-toggle-chevron{transform:rotate(45deg)}.plan-your-day__start-panel{will-change:height}.plan-your-day [data-plan-preview-card][aria-busy=true] .plan-your-day__map-wrap,.plan-your-day [data-plan-preview-card][aria-busy=true] .plan-your-day__summary,.plan-your-day [data-plan-trip-region][aria-busy=true],.plan-your-day__category-results-scroll[aria-busy=true]{opacity:.72;filter:saturate(.88)}.plan-your-day [data-plan-preview-card][aria-busy=true],.plan-your-day [data-plan-trip-region][aria-busy=true],.plan-your-day__category-results-scroll[aria-busy=true]{cursor:progress}.plan-your-day__result-distance{margin:0;font-size:.88rem;font-weight:700;color:var(--pyd-accent)}.plan-your-day__results-list,.plan-your-day__trip-list{margin:0;padding:0;list-style:none}.plan-your-day__load-more{display:flex;justify-content:center;margin-top:.85rem}.plan-your-day__result-item,.plan-your-day__trip-item{display:flex;flex-wrap:wrap;gap:.8rem;position:relative;padding:.85rem;border:thin solid var(--pyd-border-medium);border-radius:.5rem;background:var(--pyd-item-bg);transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease}.plan-your-day__result-copy,.plan-your-day__trip-copy{display:grid;gap:.15rem;min-width:0}.plan-your-day__result-copy h4,.plan-your-day__trip-copy h4{font-size:1rem;margin:0;font-weight:700}.plan-your-day__result-tools,.plan-your-day__trip-tools{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.plan-your-day__category-search-button,.plan-your-day__clear-link,.plan-your-day__load-more-button,.plan-your-day__maps-link,.plan-your-day__reorder-disabled,.plan-your-day__result-add,.plan-your-day__result-link,.plan-your-day__submit,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;height:42px;padding:.65rem .95rem;border:thin solid transparent;border-radius:999px;appearance:none;cursor:pointer;font-family:inherit;font-size:.9rem;font-weight:700;line-height:1;text-decoration:none;transition:transform .18s ease,background-color .18s ease,color .18s ease,border-color .18s ease,box-shadow .18s ease}.plan-your-day__load-more-button,.plan-your-day__maps-link,.plan-your-day__result-link,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{border-color:var(--pyd-border-strong);background:var(--pyd-panel-bg);color:var(--pyd-text)}.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__maps-link:active:not(.is-disabled),.plan-your-day__maps-link:hover:not(.is-disabled),.plan-your-day__result-link:active,.plan-your-day__result-link:hover,.plan-your-day__trip-tools a:active,.plan-your-day__trip-tools a:hover,.plan-your-day__trip-tools button:active:not(:disabled),.plan-your-day__trip-tools button:hover:not(:disabled){transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__category-search-button,.plan-your-day__load-more-button,.plan-your-day__result-add,.plan-your-day__submit{background:var(--pyd-primary-bg);color:var(--pyd-primary-text)}.plan-your-day__category-search-button:active,.plan-your-day__category-search-button:hover,.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__result-add:active,.plan-your-day__result-add:hover,.plan-your-day__submit:active,.plan-your-day__submit:hover{transform:translateY(-1px);background:var(--pyd-primary-hover-bg)}.plan-your-day__load-more-button{min-width:11rem}.plan-your-day__load-more-button.is-loading,.plan-your-day__load-more-button:disabled{cursor:progress;opacity:.82;transform:none}.plan-your-day__result-added{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;padding:.65rem .95rem;border-radius:999px;background:var(--pyd-chip-bg);color:var(--pyd-text);font-size:.9rem;font-weight:700}.plan-your-day__load-more{display:flex;justify-content:flex-start;margin-top:.9rem}.plan-your-day__load-more-button.is-loading,.plan-your-day__load-more-button:disabled{opacity:.72;cursor:progress;transform:none}.plan-your-day__waypoint-feedback{position:absolute;top:.65rem;right:.65rem;z-index:2;max-width:calc(100% - 1.3rem);padding:.45rem .7rem;border:thin solid var(--pyd-feedback-border);border-radius:.5rem;background:var(--pyd-primary-bg);box-shadow:var(--pyd-feedback-shadow);color:var(--pyd-primary-text);font-size:.85rem;font-weight:700;line-height:1.2;pointer-events:none}.plan-your-day.is-enhanced .plan-your-day__trip-item:hover{border-color:var(--pyd-accent-border);box-shadow:var(--pyd-item-hover-shadow)}.plan-your-day__trip-item.is-dragging{opacity:.7;transform:scale(.985);box-shadow:var(--pyd-item-drag-shadow)}.plan-your-day__trip-item.is-drop-before{box-shadow:inset 0 4px 0 var(--pyd-accent)}.plan-your-day__trip-item.is-drop-after{box-shadow:inset 0 -4px 0 var(--pyd-accent)}.plan-your-day__trip-main{display:grid;grid-template-columns:auto 1fr;gap:.75rem}.plan-your-day__trip-number{display:inline-flex;align-items:center;justify-content:center;width:2.1rem;height:2.1rem;border-radius:999px;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);font-size:.95rem;font-weight:700}.plan-your-day__drag-handle{display:none;align-items:center;justify-content:center;min-width:4rem;padding:.6rem 16px .4rem 32px;border-radius:1000px;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;position:relative;height:42px}.plan-your-day__drag-handle:before{content:'';display:inline-block;width:10px;height:18px;background-image:repeating-radial-gradient(circle,currentColor 0 2px,transparent 2.5px 100%);background-size:5px 6px;background-position:center;position:absolute;left:8px;top:5px;bottom:5px;margin:auto 0}.plan-your-day.is-enhanced .plan-your-day__drag-handle{display:inline-flex}.plan-your-day__reorder-links{display:inline-flex;gap:.4rem;flex-wrap:wrap}.plan-your-day__reorder-disabled{opacity:.45;cursor:not-allowed;border-color:var(--pyd-border);background:var(--pyd-disabled-bg);color:var(--pyd-disabled-text)}.plan-your-day__preview-empty,.plan-your-day__results-empty,.plan-your-day__trip-empty{display:grid;gap:.55rem;place-items:start;padding:1.1rem;border:thin dashed var(--pyd-empty-border);border-radius:.5rem;background:var(--pyd-empty-bg)}.plan-your-day__preview-empty h4,.plan-your-day__results-empty h4,.plan-your-day__trip-empty h4{font-size:1.15rem;font-weight:700}.plan-your-day__custom-start{margin-top:.85rem}.plan-your-day__custom-start label{display:inline-block;margin-bottom:.45rem;font-weight:700;color:var(--pyd-heading)}.plan-your-day__custom-start-field{position:relative}.plan-your-day__custom-start input{width:100%;min-height:2.85rem;padding:.75rem 2.85rem .75rem .9rem;border:thin solid var(--pyd-border-strong);border-radius:.5rem;background:var(--pyd-panel-bg);color:var(--pyd-heading)}.plan-your-day__custom-start-indicator{position:absolute;top:50%;right:.9rem;display:none;width:1rem;height:1rem;pointer-events:none;transform:translateY(-50%)}.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator,.plan-your-day__custom-start[data-plan-custom-start-state=found] .plan-your-day__custom-start-indicator,.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator{display:block}.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator{border:2px solid var(--pyd-border-emphasis);border-top-color:var(--pyd-accent);border-radius:999px;animation:plan-your-day-spin .8s linear infinite}.plan-your-day__custom-start[data-plan-custom-start-state=found] .plan-your-day__custom-start-indicator::before{content:"";position:absolute;top:.05rem;left:.3rem;width:.38rem;height:.72rem;border-right:.18rem solid var(--pyd-success);border-bottom:.18rem solid var(--pyd-success);transform:rotate(45deg)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator{border:2px solid var(--pyd-danger);border-radius:999px}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::after,.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::before{content:"";position:absolute;top:.2rem;left:.4rem;width:.15rem;height:.52rem;border-radius:999px;background:var(--pyd-danger)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::before{transform:rotate(45deg)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::after{transform:rotate(-45deg)}@keyframes plan-your-day-spin{to{transform:translateY(-50%) rotate(360deg)}}@media (prefers-reduced-motion:reduce){.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator{animation:none}}.plan-your-day__auto-note,.plan-your-day__input-help,.plan-your-day__noscript-note{margin-top:.55rem}.plan-your-day__actions{display:flex;gap:.75rem;flex-wrap:wrap;margin-top:.85rem}.plan-your-day__clear-link{border-color:var(--pyd-border);background:0 0;color:var(--pyd-text)}.plan-your-day__clear-link:active,.plan-your-day__clear-link:hover{transform:translateY(-1px);border-color:var(--pyd-border-emphasis);background:var(--pyd-summary-tile-bg)}.plan-your-day__maps-link.is-disabled,.plan-your-day__maps-link[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}.plan-your-day__messages{display:grid;gap:.7rem;margin-bottom:1rem}.plan-your-day__message{padding:.75rem .85rem;border-radius:.5rem;border:thin solid transparent;font-size:.92rem;line-height:1.45}.plan-your-day__message--note{border-color:var(--pyd-note-border);background:var(--pyd-note-bg);color:var(--pyd-note-text)}.plan-your-day__message--warning{border-color:var(--pyd-warning-border);background:var(--pyd-warning-bg);color:var(--pyd-warning-text)}.plan-your-day__summary{margin-top:1rem;margin-bottom:0;padding:.9rem;border-radius:.5rem;background:var(--pyd-summary-bg);border:thin solid var(--pyd-border-subtle)}.plan-your-day__summary-handoff{display:flex;flex-direction:column;gap:.45rem;margin-top:1rem}.plan-your-day__summary-handoff-label{margin:0;font-size:.84rem;font-weight:700;letter-spacing:0;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__maps-link--summary{width:100%;min-height:3.35rem;padding:.9rem 1.2rem;font-size:1.15rem}.plan-your-day__summary-grid div{padding:.7rem .8rem;border-radius:.5rem;background:var(--pyd-summary-tile-bg)}.plan-your-day__summary-grid dt{margin-bottom:.35rem;font-size:.84rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__summary-grid dd{margin:0;font-size:.95rem;color:var(--pyd-heading)}.plan-your-day__map-wrap{overflow:hidden;border-radius:1rem;border:thin solid var(--pyd-border);background:var(--pyd-map-bg)}.plan-your-day__map-frame{display:block;width:100%;min-height:24rem;border:0;background:var(--pyd-map-bg)}@media (min-width:980px){.plan-your-day__hero{grid-template-columns:minmax(0,1fr) auto;align-items:end}.plan-your-day__summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.plan-your-day__result-item,.plan-your-day__trip-item{grid-template-columns:minmax(0,1fr) auto}.plan-your-day__trip-tools{align-self:center;justify-content:end}}@container (min-width:60rem){.plan-your-day__layout{grid-template-columns:minmax(18rem,30rem) minmax(0,1fr)}.plan-your-day__preview-card{position:sticky;top:7.75rem}}@media (max-width:979px){.plan-your-day__card-header,.plan-your-day__summary-top{flex-direction:column;align-items:start}}@media (max-width:639px){.plan-your-day__card,.plan-your-day__surface{border-radius:1rem}.plan-your-day__surface{padding:1rem}.plan-your-day__category-option-body,.plan-your-day__start-option-body{padding-left:2.75rem}.plan-your-day__category-trigger{align-items:start}.plan-your-day__category-trigger-side{gap:.55rem}.plan-your-day__category-results-scroll{max-height:min(22rem,55vh)}.plan-your-day__actions,.plan-your-day__reorder-links,.plan-your-day__result-tools,.plan-your-day__trip-tools{width:100%}.plan-your-day__category-search-button,.plan-your-day__clear-link,.plan-your-day__maps-link,.plan-your-day__reorder-disabled,.plan-your-day__result-add,.plan-your-day__result-link,.plan-your-day__submit,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{width:100%}.plan-your-day__summary-grid{grid-template-columns:1fr}.plan-your-day__map-frame{min-height:20rem}}@media (min-width:768px){.plan-your-day__category-description{display:block}.plan-your-day__category-search-controls{grid-template-columns:minmax(0,1fr) auto;align-items:center}}@media (max-width:767px){.plan-your-day__category-description{display:none}} \ No newline at end of file diff --git a/plugin/plan-your-day/assets/fonts/OFL.txt b/plugin/plan-your-day/assets/fonts/OFL.txt new file mode 100644 index 0000000..09f020b --- /dev/null +++ b/plugin/plan-your-day/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-300.woff2 b/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-300.woff2 new file mode 100644 index 0000000..82d39a5 Binary files /dev/null and b/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-300.woff2 differ diff --git a/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-700.woff2 b/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-700.woff2 new file mode 100644 index 0000000..438cef7 Binary files /dev/null and b/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-700.woff2 differ diff --git a/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-regular.woff2 b/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-regular.woff2 new file mode 100644 index 0000000..107b605 Binary files /dev/null and b/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-regular.woff2 differ diff --git a/plugin/plan-your-day/assets/icons/caret-down-solid.svg b/plugin/plan-your-day/assets/icons/caret-down-solid.svg deleted file mode 100644 index 857fe24..0000000 --- a/plugin/plan-your-day/assets/icons/caret-down-solid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugin/plan-your-day/assets/icons/caret-right-solid.svg b/plugin/plan-your-day/assets/icons/caret-right-solid.svg deleted file mode 100644 index 4512fc8..0000000 --- a/plugin/plan-your-day/assets/icons/caret-right-solid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugin/plan-your-day/assets/icons/grip-vertical-solid.svg b/plugin/plan-your-day/assets/icons/grip-vertical-solid.svg deleted file mode 100644 index ff36005..0000000 --- a/plugin/plan-your-day/assets/icons/grip-vertical-solid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugin/plan-your-day/assets/js/admin-settings.js b/plugin/plan-your-day/assets/js/admin-settings.js index 645fea0..94fa741 100644 --- a/plugin/plan-your-day/assets/js/admin-settings.js +++ b/plugin/plan-your-day/assets/js/admin-settings.js @@ -1,43 +1,335 @@ (() => { - const topButton = document.querySelector('[data-plan-back-to-top]'); - const topTarget = document.getElementById('plan-your-day-settings-top'); - const topHeading = document.getElementById('plan-your-day-settings-title'); + const CATEGORY_ROW_SELECTOR = '[data-plan-category-row]'; + const CATEGORY_DRAG_HANDLE_SELECTOR = '[data-plan-category-drag-handle]'; - if (!(topButton instanceof HTMLButtonElement) || !(topTarget instanceof HTMLElement)) { - return; - } + const getCategoryRows = (rows) => Array.from(rows.querySelectorAll(CATEGORY_ROW_SELECTOR)); + + const syncCategorySortOrders = (rows) => { + getCategoryRows(rows).forEach((row, rowIndex) => { + const sortInput = row.querySelector('[data-plan-category-sort-order]'); + + if (sortInput instanceof HTMLInputElement) { + sortInput.value = String((rowIndex + 1) * 10); + } + }); + }; + + const moveCategoryRow = (rows, row, direction) => { + if (!(row instanceof HTMLTableRowElement)) { + return false; + } + + if ('up' === direction) { + const previousRow = row.previousElementSibling; + + if (previousRow instanceof HTMLTableRowElement) { + rows.insertBefore(row, previousRow); + return true; + } + } else { + const nextRow = row.nextElementSibling; + + if (nextRow instanceof HTMLTableRowElement) { + rows.insertBefore(nextRow, row); + return true; + } + } + + return false; + }; + + const getDeletedCategoryFocusTarget = (row, addButton) => { + const focusRow = + row.nextElementSibling instanceof HTMLTableRowElement + ? row.nextElementSibling + : row.previousElementSibling instanceof HTMLTableRowElement + ? row.previousElementSibling + : null; + const dragHandle = focusRow?.querySelector(CATEGORY_DRAG_HANDLE_SELECTOR); + + return dragHandle instanceof HTMLButtonElement ? dragHandle : addButton; + }; + + const initializeJQueryCategorySorting = (rows, syncSortOrders) => { + const jQuery = window.jQuery; + + if ('function' !== typeof jQuery || !jQuery.fn || 'function' !== typeof jQuery.fn.sortable) { + return null; + } + + const sortableRows = jQuery(rows); + + sortableRows.sortable({ + axis: 'y', + cancel: 'input, textarea, select, option, [data-plan-delete-category]', + handle: CATEGORY_DRAG_HANDLE_SELECTOR, + items: CATEGORY_ROW_SELECTOR, + tolerance: 'pointer', + start: (event, ui) => { + if (ui && ui.item) { + ui.item.addClass('is-dragging'); + } + }, + stop: (event, ui) => { + if (ui && ui.item) { + ui.item.removeClass('is-dragging'); + } - const toggleVisibility = () => { - const isVisible = window.scrollY > 280; + syncSortOrders(); + }, + }); - topButton.hidden = !isVisible; - topButton.classList.toggle('is-visible', isVisible); + return { + configureRow: (row, dragHandle) => { + if (dragHandle instanceof HTMLButtonElement) { + dragHandle.draggable = false; + } + }, + refresh: () => { + sortableRows.sortable('refresh'); + }, + }; }; - topButton.addEventListener('click', () => { - topTarget.scrollIntoView({ - behavior: 'smooth', - block: 'start', + const initializeVanillaCategorySorting = (rows, syncSortOrders) => { + let draggedRow = null; + + const findRowAfterPointer = (clientY) => { + return getCategoryRows(rows) + .filter((row) => row !== draggedRow) + .reduce( + (closest, row) => { + const box = row.getBoundingClientRect(); + const offset = clientY - box.top - box.height / 2; + + if (offset < 0 && offset > closest.offset) { + return { + offset, + row, + }; + } + + return closest; + }, + { + offset: Number.NEGATIVE_INFINITY, + row: null, + } + ).row; + }; + + rows.addEventListener('dragover', (event) => { + if (null === draggedRow) { + return; + } + + event.preventDefault(); + + const nextRow = findRowAfterPointer(event.clientY); + + if (nextRow instanceof HTMLTableRowElement) { + rows.insertBefore(draggedRow, nextRow); + } else { + rows.appendChild(draggedRow); + } + }); + + rows.addEventListener('drop', (event) => { + if (null === draggedRow) { + return; + } + + event.preventDefault(); + syncSortOrders(); }); - if (topHeading instanceof HTMLElement) { - window.setTimeout(() => { - topHeading.focus({ - preventScroll: true, + return { + configureRow: (row, dragHandle) => { + if (!(dragHandle instanceof HTMLButtonElement)) { + return; + } + + dragHandle.draggable = true; + + dragHandle.addEventListener('dragstart', (event) => { + draggedRow = row; + row.classList.add('is-dragging'); + + if (null !== event.dataTransfer) { + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', ''); + } + }); + + dragHandle.addEventListener('dragend', () => { + row.classList.remove('is-dragging'); + draggedRow = null; + syncSortOrders(); }); - }, 180); + }, + refresh: () => {}, + }; + }; + + const initializeCategorySorting = (rows, syncSortOrders) => { + const jquerySorting = initializeJQueryCategorySorting(rows, syncSortOrders); + + if (null !== jquerySorting) { + return jquerySorting; } - }); - - window.addEventListener( - 'scroll', - () => { - toggleVisibility(); - }, - { - passive: true, + + return initializeVanillaCategorySorting(rows, syncSortOrders); + }; + + const initializeCategoryEditor = (editor) => { + const rows = editor.querySelector('[data-plan-category-rows]'); + const template = editor.querySelector('[data-plan-category-row-template]'); + const addButton = editor.querySelector('[data-plan-add-category]'); + + if (!(rows instanceof HTMLElement) || !(template instanceof HTMLTemplateElement) || !(addButton instanceof HTMLButtonElement)) { + return; } - ); - toggleVisibility(); + let nextIndex = rows.querySelectorAll('tr').length; + const syncSortOrders = () => { + syncCategorySortOrders(rows); + }; + const sorter = initializeCategorySorting(rows, syncSortOrders); + const syncAndRefresh = () => { + syncSortOrders(); + sorter.refresh(); + }; + + const initializeRow = (row) => { + if (!(row instanceof HTMLTableRowElement) || 'true' === row.dataset.planCategoryRowReady) { + return; + } + + const dragHandle = row.querySelector(CATEGORY_DRAG_HANDLE_SELECTOR); + const deleteButton = row.querySelector('[data-plan-delete-category]'); + + if (!(dragHandle instanceof HTMLButtonElement)) { + return; + } + + row.dataset.planCategoryRowReady = 'true'; + sorter.configureRow(row, dragHandle); + + if (deleteButton instanceof HTMLButtonElement) { + deleteButton.addEventListener('click', () => { + const message = editor.getAttribute('data-plan-delete-category-confirm') || ''; + + if ('' !== message && !window.confirm(message)) { + return; + } + + const focusTarget = getDeletedCategoryFocusTarget(row, addButton); + + row.remove(); + syncAndRefresh(); + focusTarget.focus({ preventScroll: true }); + }); + } + + dragHandle.addEventListener('keydown', (event) => { + if ('ArrowUp' === event.key) { + event.preventDefault(); + + if (moveCategoryRow(rows, row, 'up')) { + syncAndRefresh(); + } + + dragHandle.focus(); + } + + if ('ArrowDown' === event.key) { + event.preventDefault(); + + if (moveCategoryRow(rows, row, 'down')) { + syncAndRefresh(); + } + + dragHandle.focus(); + } + }); + }; + + addButton.addEventListener('click', () => { + const markup = template.innerHTML.replace(/__INDEX__/g, String(nextIndex)); + rows.insertAdjacentHTML('beforeend', markup); + initializeRow(rows.lastElementChild); + nextIndex += 1; + syncAndRefresh(); + }); + + getCategoryRows(rows).forEach((row) => { + initializeRow(row); + }); + syncAndRefresh(); + }; + + const initializeCategoryEditors = () => { + document.querySelectorAll('[data-plan-category-editor]').forEach((editor) => { + if (editor instanceof HTMLElement) { + initializeCategoryEditor(editor); + } + }); + }; + + const initializeBackToTop = () => { + const topButton = document.querySelector('[data-plan-back-to-top]'); + const topTarget = document.getElementById('plan-your-day-settings-top'); + const topHeading = document.getElementById('plan-your-day-settings-title'); + + if (!(topButton instanceof HTMLButtonElement) || !(topTarget instanceof HTMLElement)) { + return; + } + + const toggleVisibility = () => { + const isVisible = window.scrollY > 280; + + topButton.hidden = !isVisible; + topButton.classList.toggle('is-visible', isVisible); + }; + + topButton.addEventListener('click', () => { + topTarget.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + + if (topHeading instanceof HTMLElement) { + window.setTimeout(() => { + topHeading.focus({ + preventScroll: true, + }); + }, 180); + } + }); + + window.addEventListener( + 'scroll', + () => { + toggleVisibility(); + }, + { + passive: true, + } + ); + + toggleVisibility(); + }; + + const initialize = () => { + initializeCategoryEditors(); + initializeBackToTop(); + }; + + if ('loading' === document.readyState) { + document.addEventListener('DOMContentLoaded', initialize, { + once: true, + }); + } else { + initialize(); + } })(); diff --git a/plugin/plan-your-day/assets/js/plan-block.js b/plugin/plan-your-day/assets/js/plan-block.js index 5dfed4c..ece62ed 100644 --- a/plugin/plan-your-day/assets/js/plan-block.js +++ b/plugin/plan-your-day/assets/js/plan-block.js @@ -43,7 +43,7 @@ el( Placeholder, { - label: __( 'Plan Your Day', 'plan-your-day' ), + label: __( 'Waypoints', 'plan-your-day' ), instructions: __( 'The frontend uses the same planner renderer as the shortcode. Configure an optional action URL in the block settings.', 'plan-your-day' ), }, el( diff --git a/plugin/plan-your-day/assets/js/plan.js b/plugin/plan-your-day/assets/js/plan.js index 7f6b256..be56978 100644 --- a/plugin/plan-your-day/assets/js/plan.js +++ b/plugin/plan-your-day/assets/js/plan.js @@ -2,6 +2,20 @@ const ROOT_SELECTOR = '[data-plan-root]'; const ENHANCED_FLAG = 'planYourDayEnhanced'; const START_PANEL_ANIMATION_MS = 480; + const COLOR_MODE_STORAGE_KEY = 'planYourDayColorMode'; + const COLOR_MODE_LIGHT = 'light'; + const COLOR_MODE_DARK = 'dark'; + const COLOR_MODE_SYSTEM = 'system'; + const COLOR_MODE_VALUES = [COLOR_MODE_LIGHT, COLOR_MODE_DARK]; + const COLOR_MODE_DEFAULT_VALUES = [...COLOR_MODE_VALUES, COLOR_MODE_SYSTEM]; + const CUSTOM_START_STATUS_CHECKING = 'checking'; + const CUSTOM_START_STATUS_FOUND = 'found'; + const CUSTOM_START_STATUS_NOT_FOUND = 'not_found'; + const CUSTOM_START_STATUSES = [ + CUSTOM_START_STATUS_CHECKING, + CUSTOM_START_STATUS_FOUND, + CUSTOM_START_STATUS_NOT_FOUND, + ]; const parseConfig = (root) => { const configElement = root.querySelector('[data-plan-config]'); @@ -100,12 +114,109 @@ }); }; + const isColorMode = (value) => COLOR_MODE_VALUES.includes(String(value || '')); + + const normalizeColorModeDefault = (value) => { + const colorModeDefault = String(value || ''); + + return COLOR_MODE_DEFAULT_VALUES.includes(colorModeDefault) ? colorModeDefault : COLOR_MODE_LIGHT; + }; + + const getStoredColorMode = () => { + try { + const storedMode = window.localStorage?.getItem(COLOR_MODE_STORAGE_KEY); + + return isColorMode(storedMode) ? storedMode : ''; + } catch (error) { + return ''; + } + }; + + const setStoredColorMode = (colorMode) => { + if (!isColorMode(colorMode)) { + return; + } + + try { + window.localStorage?.setItem(COLOR_MODE_STORAGE_KEY, colorMode); + } catch (error) { + // Local storage can be blocked; the in-page mode still applies. + } + }; + + const getColorModeMediaQuery = () => { + if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { + return null; + } + + return window.matchMedia('(prefers-color-scheme: dark)'); + }; + + const resolveColorMode = (colorModeDefault, mediaQuery = getColorModeMediaQuery()) => { + const storedMode = getStoredColorMode(); + + if (isColorMode(storedMode)) { + return storedMode; + } + + const defaultMode = normalizeColorModeDefault(colorModeDefault); + + if (defaultMode === COLOR_MODE_SYSTEM) { + return mediaQuery?.matches ? COLOR_MODE_DARK : COLOR_MODE_LIGHT; + } + + return defaultMode; + }; + + const syncColorModeToggle = (refs, colorMode, strings) => { + if (!(refs.colorModeToggle instanceof HTMLButtonElement)) { + return; + } + + refs.colorModeToggle.hidden = false; + refs.colorModeToggle.setAttribute('aria-pressed', String(colorMode === COLOR_MODE_DARK)); + refs.colorModeToggle.setAttribute('aria-label', String(strings.darkModeLabel || 'Dark mode')); + + if (refs.colorModeToggleLabel instanceof HTMLElement) { + refs.colorModeToggleLabel.textContent = String(strings.darkModeLabel || 'Dark mode'); + } + }; + + const applyColorMode = (root, refs, colorMode, strings) => { + const nextMode = isColorMode(colorMode) ? colorMode : COLOR_MODE_LIGHT; + + root.setAttribute('data-plan-color-mode', nextMode); + syncColorModeToggle(refs, nextMode, strings); + }; + const getCheckedStartMode = (startModeInputs) => { const checkedInput = startModeInputs.find((input) => input.checked); return checkedInput ? checkedInput.value : ''; }; + const normalizeCustomStartStatus = (status) => { + const normalizedStatus = String(status || ''); + + return CUSTOM_START_STATUSES.includes(normalizedStatus) ? normalizedStatus : ''; + }; + + const getCustomStartStatusMessage = (status, strings) => { + if (status === CUSTOM_START_STATUS_CHECKING) { + return String(strings.customStartChecking || 'Checking starting address.'); + } + + if (status === CUSTOM_START_STATUS_FOUND) { + return String(strings.customStartFound || 'Starting address found. Results are ready.'); + } + + if (status === CUSTOM_START_STATUS_NOT_FOUND) { + return String(strings.customStartNotFound || 'Starting address was not found.'); + } + + return ''; + }; + const buildPayload = (refs, state) => { const formData = refs.form instanceof HTMLFormElement ? new FormData(refs.form) : new FormData(); @@ -703,7 +814,19 @@ const shouldRefreshBrowseRoute = (routeData) => toStringArray(routeData?.selectedWaypointIds).length === 0; - const syncStartUi = (refs, state) => { + const syncCustomStartStatusUi = (refs, status, strings) => { + const normalizedStatus = normalizeCustomStartStatus(status); + + if (refs.customStartWrap instanceof HTMLElement) { + refs.customStartWrap.setAttribute('data-plan-custom-start-state', normalizedStatus); + } + + if (refs.customStartStatus instanceof HTMLElement) { + refs.customStartStatus.textContent = getCustomStartStatusMessage(normalizedStatus, strings); + } + }; + + const syncStartUi = (refs, state, strings) => { refs.startModeInputs.forEach((input) => { input.checked = input.value === state.startMode; }); @@ -722,6 +845,8 @@ if (refs.customStartInput) { refs.customStartInput.disabled = !isCustom; } + + syncCustomStartStatusUi(refs, isCustom ? state.customStartStatus : '', strings); }; const syncCategorySearchUi = (refs, state) => { @@ -850,9 +975,12 @@ startModeInputs: Array.from(root.querySelectorAll('input[name="start_mode"]')), customStartWrap: root.querySelector('[data-plan-custom-start-wrap]'), customStartInput: root.querySelector('[data-plan-custom-start]'), + customStartStatus: root.querySelector('[data-plan-custom-start-status]'), startToggle: root.querySelector('[data-plan-start-toggle]'), startToggleLabel: root.querySelector('[data-plan-start-toggle-label]'), startPanel: root.querySelector('[data-plan-start-panel]'), + colorModeToggle: root.querySelector('[data-plan-color-mode-toggle]'), + colorModeToggleLabel: root.querySelector('[data-plan-color-mode-toggle-label]'), tripHeaderActions: root.querySelector('[data-plan-trip-header-actions]'), tripHeading: root.querySelector('[data-plan-trip-heading]'), tripRegion: root.querySelector('[data-plan-trip-region]'), @@ -873,36 +1001,55 @@ categorySearch: String(config.initialState?.categorySearch || ''), startMode: String(config.initialState?.startMode || 'default'), customStart: String(config.initialState?.customStart || ''), + customStartStatus: normalizeCustomStartStatus(config.initialData?.browse?.customStartStatus || ''), expandedCategory: String(config.initialState?.category || ''), customResultsExpanded: Boolean(config.initialData?.browse?.isCustomSearch), isLoadingMore: false, browse: config.initialData?.browse || {}, route: config.initialData?.route || {}, }; + const canBootstrapEndpointToken = + typeof config.rest?.bootstrapUrl === 'string' && + config.rest.bootstrapUrl !== ''; + let endpointToken = typeof config.rest?.endpointToken === 'string' ? config.rest.endpointToken : ''; + let hasBootstrappedEndpointToken = false; + let endpointTokenRequest = null; const hasRestConfig = refs.form instanceof HTMLFormElement && typeof config.rest?.browseUrl === 'string' && config.rest.browseUrl !== '' && typeof config.rest?.routeUrl === 'string' && config.rest.routeUrl !== '' && - typeof config.rest?.endpointToken === 'string' && - config.rest.endpointToken !== ''; + (endpointToken !== '' || canBootstrapEndpointToken); const shouldHydrateOnLoad = Boolean(config.hydration?.shouldHydrateOnLoad); + const colorModeDefault = normalizeColorModeDefault( + config.colorModeDefault || root.getAttribute('data-plan-color-mode-default') + ); + const colorModeMediaQuery = getColorModeMediaQuery(); let isStartPanelOpen = true; let activeRequestController = null; let activeRequestEndpointKey = ''; let activeRequestId = 0; let pendingRouteFocusRequest = null; - let hasTouchedStartSelection = false; - let hasAutoCollapsedDefaultStart = false; - const initialWindowScrollY = typeof window === 'undefined' ? 0 : window.scrollY; const prefersReducedMotion = typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches; let startPanelAnimationFrame = 0; + applyColorMode(root, refs, resolveColorMode(colorModeDefault, colorModeMediaQuery), strings); + + if (colorModeDefault === COLOR_MODE_SYSTEM && colorModeMediaQuery) { + colorModeMediaQuery.addEventListener('change', () => { + if (getStoredColorMode()) { + return; + } + + applyColorMode(root, refs, resolveColorMode(colorModeDefault, colorModeMediaQuery), strings); + }); + } + const renderAll = () => { renderCategoryPanels(refs, state, strings, { isLoadingMore: state.isLoadingMore, @@ -910,7 +1057,7 @@ renderTrip(refs, state, strings); renderPreview(refs, state, strings); syncHiddenInputs(refs, state); - syncStartUi(refs, state); + syncStartUi(refs, state, strings); syncCategorySearchUi(refs, state); setRouteMutationBusyState(root, false); }; @@ -953,23 +1100,7 @@ ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2; - const getViewportTopOffset = () => { - const adminBar = document.getElementById('wpadminbar'); - - return (adminBar instanceof HTMLElement ? adminBar.offsetHeight : 0) + 16; - }; - - const getStartSelectionScrollTargetY = (closingHeight) => { - if (!(refs.resultsHeading instanceof HTMLElement) || typeof window === 'undefined') { - return null; - } - - const headingTop = refs.resultsHeading.getBoundingClientRect().top; - - return Math.max(window.scrollY + headingTop - getViewportTopOffset() - closingHeight, 0); - }; - - const animateStartPanel = (nextOpenState, options = {}) => { + const animateStartPanel = (nextOpenState) => { if (!(refs.startPanel instanceof HTMLElement) || !refs.startToggle) { isStartPanelOpen = nextOpenState; updateStartPanelState(); @@ -978,7 +1109,6 @@ const panel = refs.startPanel; const duration = prefersReducedMotion ? 0 : START_PANEL_ANIMATION_MS; - const scrollToY = typeof options.scrollToY === 'number' ? options.scrollToY : null; if (startPanelAnimationFrame) { window.cancelAnimationFrame(startPanelAnimationFrame); @@ -1013,13 +1143,6 @@ panel.hidden = !nextOpenState; updateStartPanelState(); - if (scrollToY !== null && typeof window !== 'undefined') { - window.scrollTo({ - top: scrollToY, - behavior: 'auto', - }); - } - return; } @@ -1033,13 +1156,6 @@ panel.hidden = !nextOpenState; updateStartPanelState(); - if (scrollToY !== null && typeof window !== 'undefined') { - window.scrollTo({ - top: scrollToY, - behavior: 'auto', - }); - } - startPanelAnimationFrame = 0; }; @@ -1071,37 +1187,64 @@ animateStartPanel(true); }; - const closeStartPanel = (options = {}) => { + const closeStartPanel = () => { if (!isStartPanelOpen) { return; } - const currentHeight = refs.startPanel instanceof HTMLElement ? refs.startPanel.getBoundingClientRect().height : 0; - const scrollToY = options.scrollToResults ? getStartSelectionScrollTargetY(currentHeight) : null; - - animateStartPanel(false, { - scrollToY, - }); + animateStartPanel(false); }; - const maybeAutoCollapseDefaultStart = () => { - if ( - typeof window === 'undefined' || - hasTouchedStartSelection || - hasAutoCollapsedDefaultStart || - !isStartPanelOpen || - state.startMode !== 'default' || - String(state.customStart || '').trim() !== '' - ) { - return; + const ensureEndpointToken = async () => { + if (!canBootstrapEndpointToken) { + return endpointToken; } - if (window.scrollY - initialWindowScrollY < 100) { - return; + if (hasBootstrappedEndpointToken && endpointToken !== '') { + return endpointToken; + } + + if (endpointTokenRequest) { + return endpointTokenRequest; } - hasAutoCollapsedDefaultStart = true; - closeStartPanel(); + endpointTokenRequest = fetch(config.rest.bootstrapUrl, { + method: 'POST', + credentials: 'same-origin', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }) + .then(async (response) => { + const responseBody = await response.json().catch(() => ({})); + debugLog(config, response.ok ? 'info' : 'warn', 'request:bootstrap', { + status: response.status, + ok: response.ok, + body: responseBody, + }); + + if (!response.ok) { + throw new Error(responseBody?.message || strings.requestFailed || ''); + } + + const freshToken = String(responseBody?.endpointToken || ''); + + if (freshToken === '') { + throw new Error(strings.requestFailed || ''); + } + + endpointToken = freshToken; + hasBootstrappedEndpointToken = true; + + return endpointToken; + }) + .finally(() => { + endpointTokenRequest = null; + }); + + return endpointTokenRequest; }; const sendRequest = async (endpointKey, payload, requestOptions = {}) => { @@ -1115,6 +1258,19 @@ const searchContextKey = String(requestOptions.searchContextKey || ''); const refreshRoute = endpointKey === 'browse' ? requestOptions.refreshRoute !== false : false; const routeFocusRequest = endpointKey === 'route' ? requestOptions.routeFocusRequest ?? null : null; + const payloadStartMode = String(payload.start_mode || ''); + const payloadCustomStart = String(payload.custom_start || ''); + const shouldCheckCustomStart = + endpointKey === 'browse' && + !appendBrowseResults && + payloadStartMode === 'custom' && + payloadCustomStart.trim() !== ''; + const shouldClearRouteCustomStartStatus = + endpointKey === 'route' && + (state.customStartStatus === CUSTOM_START_STATUS_CHECKING || + payloadStartMode !== 'custom' || + payloadCustomStart.trim() === '' || + payloadCustomStart !== String(state.customStart || '')); if (activeRequestController instanceof AbortController) { if (activeRequestEndpointKey === 'route') { @@ -1147,15 +1303,28 @@ setRegionBusyState(refs, state, true); setRouteMutationBusyState(root, endpointKey === 'route'); setBrowseControlsBusyState(root, endpointKey === 'route'); + if (shouldCheckCustomStart) { + state.customStartStatus = CUSTOM_START_STATUS_CHECKING; + syncStartUi(refs, state, strings); + } else if ((endpointKey === 'browse' && !appendBrowseResults) || shouldClearRouteCustomStartStatus) { + state.customStartStatus = ''; + syncStartUi(refs, state, strings); + } debugLog(config, 'info', 'request:start', { endpointKey, payload, }); try { + const requestEndpointToken = await ensureEndpointToken(); + + if (requestEndpointToken === '') { + throw new Error(strings.requestFailed || ''); + } + const requestBody = { ...payload, - endpoint_token: config.rest.endpointToken, + endpoint_token: requestEndpointToken, }; if (endpointKey === 'browse') { @@ -1225,6 +1394,7 @@ state.route = responseBody?.route || state.route || {}; state.category = String(state.browse.categoryKey || payload.category || ''); state.categorySearch = String(state.browse.categorySearch || payload.category_search || ''); + state.customStartStatus = normalizeCustomStartStatus(state.browse.customStartStatus || ''); state.isLoadingMore = false; if (state.category) { @@ -1247,7 +1417,7 @@ renderTrip(refs, state, strings); renderPreview(refs, state, strings); syncHiddenInputs(refs, state); - syncStartUi(refs, state); + syncStartUi(refs, state, strings); syncCategorySearchUi(refs, state); setRouteMutationBusyState(root, false); announce( @@ -1285,9 +1455,17 @@ debugLog(config, 'info', 'request:aborted', { endpointKey, }); + if (requestId === activeRequestId && shouldCheckCustomStart) { + state.customStartStatus = ''; + syncStartUi(refs, state, strings); + } return 'aborted'; } + if (requestId !== activeRequestId) { + return 'stale'; + } + debugLog(config, 'error', 'request:failed', { endpointKey, error: error instanceof Error ? error.message : String(error || ''), @@ -1306,6 +1484,10 @@ appendBrowseResults ? errorMessage || strings.requestFailed || '' : error instanceof Error ? error.message : strings.requestFailed || '', appendBrowseResults ? errorMessage || strings.requestFailed || '' : '' ); + if (shouldCheckCustomStart) { + state.customStartStatus = ''; + syncStartUi(refs, state, strings); + } return 'failed'; } finally { @@ -1334,12 +1516,7 @@ refs.startModeInputs.forEach((input) => { input.addEventListener('change', () => { state.startMode = getCheckedStartMode(refs.startModeInputs) || state.startMode || 'default'; - syncStartUi(refs, state); - hasTouchedStartSelection = true; - - if (state.startMode !== 'custom' || state.customStart.trim() !== '') { - closeStartPanel({ scrollToResults: true }); - } + syncStartUi(refs, state, strings); if (!hasRestConfig) { announce(refs.liveRegion, strings.startingPointUpdated || ''); @@ -1356,12 +1533,7 @@ if (refs.customStartInput instanceof HTMLInputElement) { refs.customStartInput.addEventListener('change', () => { state.customStart = refs.customStartInput.value || ''; - syncStartUi(refs, state); - hasTouchedStartSelection = true; - - if (state.startMode === 'custom' && state.customStart.trim() !== '') { - closeStartPanel({ scrollToResults: true }); - } + syncStartUi(refs, state, strings); if (!hasRestConfig) { announce(refs.liveRegion, strings.startingPointUpdated || ''); @@ -1375,10 +1547,6 @@ }); } - if (typeof window !== 'undefined') { - window.addEventListener('scroll', maybeAutoCollapseDefaultStart, { passive: true }); - } - if (refs.categorySearchInput instanceof HTMLInputElement) { refs.categorySearchInput.addEventListener('input', () => { state.categorySearch = refs.categorySearchInput.value || ''; @@ -1503,6 +1671,21 @@ return; } + const colorModeToggle = target.closest('[data-plan-color-mode-toggle]'); + + if (colorModeToggle instanceof HTMLButtonElement) { + event.preventDefault(); + + const currentMode = root.getAttribute('data-plan-color-mode') === COLOR_MODE_DARK + ? COLOR_MODE_DARK + : COLOR_MODE_LIGHT; + const nextMode = currentMode === COLOR_MODE_DARK ? COLOR_MODE_LIGHT : COLOR_MODE_DARK; + + setStoredColorMode(nextMode); + applyColorMode(root, refs, nextMode, strings); + return; + } + const customResultsButton = target.closest('[data-plan-custom-results-button]'); if (customResultsButton instanceof HTMLButtonElement) { diff --git a/plugin/plan-your-day/assets/js/plan.min.js b/plugin/plan-your-day/assets/js/plan.min.js index 8d53a33..46565b4 100644 --- a/plugin/plan-your-day/assets/js/plan.min.js +++ b/plugin/plan-your-day/assets/js/plan.min.js @@ -1 +1 @@ -(()=>{const e="planYourDayEnhanced",t=e=>String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),a=(e,t={})=>{let a=String(e||"");return Object.entries(t||{}).forEach(([e,t])=>{a=a.split(`{${e}}`).join(String(t??""))}),a},n=(e,t="")=>Array.isArray(e)?e.map(e=>n(e)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,t])=>{const a=String(e).toLowerCase();return[e,a.includes("token")||a.includes("api_key")||a.includes("authorization")||a.includes("cookie")||a.includes("secret")?"[redacted]":n(t,e)]})):"string"==typeof e?String(t).toLowerCase().includes("token")?"[redacted]":e.replace(/([?&](?:key|api_key|token)=)[^&]+/gi,"$1[redacted]"):e,r=(e,t,a,r={})=>{if(!e?.debug||"undefined"==typeof console)return;("function"==typeof console[t]?console[t]:console.log).call(console,`[plan-your-day] ${a}`,n(r))},o=e=>Array.isArray(e)?e.map(e=>String(e??"")).filter(Boolean):[],s=(e,t)=>{e&&t&&(e.textContent="",window.requestAnimationFrame(()=>{e.textContent=t}))},i=e=>{const t=e.find(e=>e.checked);return t?t.value:""},l=(e,t)=>{const a=e.form instanceof HTMLFormElement?new FormData(e.form):new FormData;return{category:String(a.get("category")||t.category||""),category_search:String(a.get("category_search")||t.categorySearch||""),waypoints:a.getAll("waypoints[]").map(e=>String(e||"")).filter(Boolean),start_mode:String(a.get("start_mode")||t.startMode||"default"),custom_start:String(a.get("custom_start")||"")}},u=(e,n,r)=>{const o=String(e?.id||""),s=String(e?.label||""),i=String(e?.address||""),l=String(e?.distance_label||""),u=String(e?.maps_uri||""),d=n.includes(o);return`\n
  • \n
    \n

    ${t(s)}

    \n ${l?`

    ${t(l)}

    `:""}\n

    ${t(i)}

    \n
    \n
    \n ${u?`${t(r.viewInGoogleMaps||"")}`:""}\n ${d?`${t(r.inTrip||"")}`:``}\n
    \n
  • \n `},d=(e,a,n=!1)=>{if(!e?.hasMoreResults)return"";const r=String(n?a.loadingMoreResults||a.moreResultsButton||"":a.moreResultsButton||"");return`\n
    \n \n
    \n `},c=(e,a,n,r={})=>`\n
    \n ${((e,a,n)=>{const r=Array.isArray(e?.searchResults)?e.searchResults:[];if(0===r.length){const a=e?.resultsEmptyState||{};return`\n
    \n

    ${t(a.heading||"")}

    \n

    ${t(a.body||"")}

    \n
    \n `}return`\n \n `})(e,a,n)}\n
    \n ${d(e,n,Boolean(r.isLoadingMore))}\n `,p=(e,t)=>{const a=new Set((e=>{const t=[];return(Array.isArray(e?.searchResults)?e.searchResults:[]).forEach(e=>{const a=String(e?.id||"");a&&!t.includes(a)&&t.push(a)}),t})(e));return(Array.isArray(t?.searchResults)?t.searchResults:[]).filter(e=>{const t=String(e?.id||"");return!t||!a.has(t)&&(a.add(t),!0)})},y=(e,a)=>{e.categoryInput&&(e.categoryInput.value=a.category||""),e.waypointInputs&&(e.waypointInputs.innerHTML=o(a.route?.selectedWaypointIds).map(e=>``).join(""))},g=(e,a)=>{if(!e.messages)return;const n=Array.isArray(a)?a:[];e.messages.hidden=0===n.length,e.messages.innerHTML=(e=>(Array.isArray(e)?e:[]).map(e=>{const a=String(e?.type||"note"),n=String(e?.text||""),r="warning"===a?"alert":"";return`
  • ${t(n)}
  • `}).join(""))(n)},m=(e,t,a,n)=>{if(!(e instanceof HTMLElement))return;const r=e.querySelector("[data-plan-load-more-wrap]"),o=d(t,a,n);o?r instanceof HTMLElement?r.outerHTML=o:e.insertAdjacentHTML("beforeend",o):r instanceof HTMLElement&&r.remove()},f=(e,t,a,n,r={})=>{if(!(e instanceof HTMLElement))return;const o=Boolean(r.appendResults),s=Array.isArray(r.appendedResults)?r.appendedResults:[];o&&(s.length>0&&((e,t,a,n)=>{if(!(e instanceof HTMLElement&&Array.isArray(t)&&0!==t.length))return!1;const r=e.querySelector("[data-plan-results-list]");if(!(r instanceof HTMLElement))return!1;const o=e.querySelector("[data-plan-results-empty]");return o instanceof HTMLElement&&o.remove(),r.insertAdjacentHTML("beforeend",t.map(e=>u(e,a,n)).join("")),!0})(e,s,a,n)||0===s.length&&(e=>e instanceof HTMLElement&&e.querySelector("[data-plan-results-list], [data-plan-results-empty]")instanceof HTMLElement)(e))?m(e,t,n,Boolean(r.isLoadingMore)):e.innerHTML=c(t,a,n,r)},h=(e,t,n,r={})=>{const s=t.category||"",i=t.expandedCategory||"",l=o(t.route?.selectedWaypointIds),u=t.browse||{},d=Boolean(u.hasSearch)&&!s,p=d||0===e.categoryButtons.length,y=d&&Boolean(t.customResultsExpanded)||!d&&0===e.categoryButtons.length,g={appendResults:Boolean(r.appendResults),appendedResults:Array.isArray(r.appendedResults)?r.appendedResults:[],isLoadingMore:Boolean(r.isLoadingMore)};e.categoryButtons.forEach(e=>{const t=e.getAttribute("data-category-key")||"",a=t===s&&t===i,n=e.closest(".plan-your-day__category-accordion-item");e.setAttribute("aria-expanded",String(a)),n instanceof HTMLElement&&n.classList.toggle("is-expanded",a)}),e.categoryRegions.forEach(e=>{const t=e.getAttribute("data-category-key")||"",a=t===s&&t===i,r=e.querySelector("[data-plan-category-results-panel]");if(e.hidden=!a,r instanceof HTMLElement){if(!a)return void(r.innerHTML="");f(r,u,l,n,g)}}),e.customResults&&(e.customResults.hidden=!p,e.customResults.classList.toggle("is-expanded",y)),e.customResultsButton&&e.customResultsButton.setAttribute("aria-expanded",String(y)),e.customResultsRegion&&(e.customResultsRegion.hidden=!y),e.customResultsHeading&&(e.customResultsHeading.textContent=d?a(n.searchResultsFor||"",{search:u.categoryLabel||""}):String(u?.resultsEmptyState?.heading||"")),e.customResultsDescription&&(e.customResultsDescription.textContent=String(d?n.customSearchResultsDescription||"":u?.resultsEmptyState?.body||"")),e.customResultsPanel&&(y?d?f(e.customResultsPanel,u,l,n,g):e.customResultsPanel.innerHTML=c(u,l,n,{isLoadingMore:!1}):e.customResultsPanel.innerHTML="")},b=(e,n,r)=>{e.tripHeaderActions&&(e.tripHeaderActions.innerHTML=((e,a)=>{const n=o(e?.selectedWaypointIds),r=String(e?.tripCountLabel||"");return`\n ${t(r)}\n ${n.length>0?``:""}\n `})(n.route,r)),e.tripRegion&&(e.tripRegion.innerHTML=((e,n,r)=>{const o=Array.isArray(e?.tripWaypoints)?e.tripWaypoints:[];if(0===o.length){const a=e?.tripEmptyState||{};return`\n
    \n

    ${t(a.heading||n.tripEmptyHeading||"")}

    \n

    ${t(a.body||n.tripEmptyBody||"")}

    \n
    \n `}return`\n
      \n ${o.map((e,r)=>{const s=String(e?.id||""),i=String(e?.label||""),l=String(e?.address||""),u=r>0,d=r\n
      \n \n
      \n

      ${t(i)}

      \n

      ${t(l)}

      \n
      \n
      \n
      \n \n ${t(n.moveUp||"")}\n \n \n ${t(n.moveDown||"")}\n \n \n
      \n \n `}).join("")}\n
    \n `})(n.route,r,e.tripRegion.getAttribute("data-plan-trip-help-id")||""))},S=(e,t,a)=>{const n=t.route||{},r=String(n.iframeSrc||""),s=n.emptyPreviewState||{},i=String(n.mapsUrl||""),l=o(n.selectedWaypointIds).length>0;g(e,n.messages),e.mapWrap&&(e.mapWrap.hidden=""===r),e.iframe&&(e.iframe.src=r),e.previewEmpty&&(e.previewEmpty.hidden=""!==r),e.previewEmptyHeading&&(e.previewEmptyHeading.textContent=String(s.heading||"")),e.previewEmptyBody&&(e.previewEmptyBody.textContent=String(s.body||"")),e.summaryCount&&(e.summaryCount.textContent=String(n.tripCountLabel||""),e.summaryCount.hidden=l),e.openLinkLabel&&(e.openLinkLabel.textContent=String(n.mapsLinkLabel||"")),e.openLink&&(e.openLink.hidden=!l,e.openLink.classList.toggle("is-disabled",""===i),i?(e.openLink.href=i,e.openLink.removeAttribute("aria-disabled"),e.openLink.removeAttribute("tabindex"),e.openLink.removeAttribute("role")):(e.openLink.removeAttribute("href"),e.openLink.setAttribute("aria-disabled","true"),e.openLink.setAttribute("tabindex","0"),e.openLink.setAttribute("role","button")))},w=e=>e instanceof HTMLElement&&"function"==typeof e.focus&&(e.focus(),document.activeElement===e),v=e=>{if(!(e instanceof HTMLButtonElement))return null;if(e.matches('[data-plan-action="add-waypoint"]'))return{action:"add-waypoint",placeId:e.getAttribute("data-place-id")||e.value||""};if(e.matches('[data-plan-action="remove-waypoint"]'))return{action:"remove-waypoint",placeId:e.getAttribute("data-place-id")||e.value||""};if(e.matches("[data-plan-clear-trip]"))return{action:"clear-trip",placeId:""};if("move_waypoint"===e.name&&e.value){const[t,a]=String(e.value).split(":",2);return{action:"move-waypoint",placeId:t||"",direction:a||""}}return null},R=e=>0===o(e?.selectedWaypointIds).length,L=(e,t)=>{e.startModeInputs.forEach(e=>{e.checked=e.value===t.startMode}),e.customStartInput&&(e.customStartInput.value=t.customStart||"");const a="custom"===(i(e.startModeInputs)||t.startMode||"default");e.customStartWrap&&(e.customStartWrap.hidden=!a),e.customStartInput&&(e.customStartInput.disabled=!a)},M=(e,t)=>{if(!(e.categorySearchInput instanceof HTMLInputElement))return;const a=String(t.categorySearch||"");e.categorySearchInput.value!==a&&(e.categorySearchInput.value=a)},E=(e,t)=>{e.classList.toggle("is-submitting",t),e.setAttribute("aria-busy",String(t))},_=(e,t,a)=>{e.forEach(e=>{if(!(e instanceof HTMLButtonElement||e instanceof HTMLInputElement))return;if(t)return e.hasAttribute(a)||e.setAttribute(a,e.disabled?"true":"false"),void(e.disabled=!0);const n=e.getAttribute(a);null!==n&&(e.disabled="true"===n,e.removeAttribute(a))})},A=(e,t)=>{_(e.querySelectorAll("[data-plan-route-mutation]"),t,"data-plan-disabled-before-request")},q=(e,t)=>{_(e.querySelectorAll(['[data-plan-form] button[type="submit"]:not([data-plan-route-mutation])',"[data-plan-load-more-button]","[data-plan-start-toggle]","[data-plan-custom-results-button]",'input[name="start_mode"]',"[data-plan-custom-start]","[data-plan-category-search]"].join(",")),t,"data-plan-browse-disabled-before-request")},T=(e,t,a)=>{const n=t.category||"";if(e.categoryPanels.forEach(t=>{const r=t.getAttribute("data-category-key")||"",o=0===e.categoryButtons.length||r===n||!1===e.customResults?.hidden;t.setAttribute("aria-busy",String(a&&o))}),e.customResultsPanel instanceof HTMLElement){const t=!1===e.customResults?.hidden;e.customResultsPanel.setAttribute("aria-busy",String(a&&t))}e.tripRegion instanceof HTMLElement&&e.tripRegion.setAttribute("aria-busy",String(a)),e.previewCard instanceof HTMLElement&&e.previewCard.setAttribute("aria-busy",String(a))},H=t=>{if(!(t instanceof HTMLElement)||"true"===t.dataset[e])return;t.dataset[e]="true";const n=(e=>{const t=e.querySelector("[data-plan-config]");if(!t)return{};try{return JSON.parse(t.textContent||"{}")}catch(e){return{}}})(t),o=n.strings||{},u={form:t.querySelector("[data-plan-form]"),liveRegion:t.querySelector("[data-plan-live-region]"),categoryInput:t.querySelector("[data-plan-category-input]"),waypointInputs:t.querySelector("[data-plan-waypoint-inputs]"),categoryButtons:Array.from(t.querySelectorAll("[data-plan-category-button]")),categoryItems:Array.from(t.querySelectorAll("[data-plan-category-item]")),categoryRegions:Array.from(t.querySelectorAll("[data-plan-category-region]")),categoryPanels:Array.from(t.querySelectorAll("[data-plan-category-results-panel]")),categorySearchInput:t.querySelector("[data-plan-category-search]"),customResults:t.querySelector("[data-plan-custom-results]"),customResultsButton:t.querySelector("[data-plan-custom-results-button]"),customResultsHeading:t.querySelector("[data-plan-custom-results-heading]"),customResultsDescription:t.querySelector("[data-plan-custom-results-description]"),customResultsRegion:t.querySelector("[data-plan-custom-results-region]"),customResultsPanel:t.querySelector("[data-plan-custom-results-panel]"),resultsHeading:t.querySelector("[data-plan-results-heading]"),startModeInputs:Array.from(t.querySelectorAll('input[name="start_mode"]')),customStartWrap:t.querySelector("[data-plan-custom-start-wrap]"),customStartInput:t.querySelector("[data-plan-custom-start]"),startToggle:t.querySelector("[data-plan-start-toggle]"),startToggleLabel:t.querySelector("[data-plan-start-toggle-label]"),startPanel:t.querySelector("[data-plan-start-panel]"),tripHeaderActions:t.querySelector("[data-plan-trip-header-actions]"),tripHeading:t.querySelector("[data-plan-trip-heading]"),tripRegion:t.querySelector("[data-plan-trip-region]"),messages:t.querySelector("[data-plan-messages]"),previewCard:t.querySelector("[data-plan-preview-card]"),mapWrap:t.querySelector("[data-plan-map-wrap]"),iframe:t.querySelector("[data-plan-iframe]"),previewEmpty:t.querySelector("[data-plan-preview-empty]"),previewEmptyHeading:t.querySelector("[data-plan-preview-empty-heading]"),previewEmptyBody:t.querySelector("[data-plan-preview-empty-body]"),summaryCount:t.querySelector("[data-plan-summary-count]"),openLink:t.querySelector("[data-plan-open-link]"),openLinkLabel:t.querySelector("[data-plan-open-link-label]")},d={category:String(n.initialState?.category||""),categorySearch:String(n.initialState?.categorySearch||""),startMode:String(n.initialState?.startMode||"default"),customStart:String(n.initialState?.customStart||""),expandedCategory:String(n.initialState?.category||""),customResultsExpanded:Boolean(n.initialData?.browse?.isCustomSearch),isLoadingMore:!1,browse:n.initialData?.browse||{},route:n.initialData?.route||{}},c=u.form instanceof HTMLFormElement&&"string"==typeof n.rest?.browseUrl&&""!==n.rest.browseUrl&&"string"==typeof n.rest?.routeUrl&&""!==n.rest.routeUrl&&"string"==typeof n.rest?.endpointToken&&""!==n.rest.endpointToken,m=Boolean(n.hydration?.shouldHydrateOnLoad);let f=!0,_=null,H="",$=0,x=null,k=!1,I=!1;const C="undefined"==typeof window?0:window.scrollY,B="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;let P=0;const D=()=>{h(u,d,o,{isLoadingMore:d.isLoadingMore}),b(u,d,o),S(u,d),y(u,d),L(u,d),M(u,d),A(t,!1)},F=(e,t="")=>{g(u,[{type:"warning",text:e||o.requestFailed||""}]),s(u.liveRegion,t||e||o.requestFailed||"")},U=(e={})=>{if(!u.startToggle||!u.startPanel)return;const t=!1!==e.syncHidden;u.startToggle.hidden=!1,u.startToggle.setAttribute("aria-expanded",String(f)),u.startToggle.classList.toggle("is-collapsed",!f),t&&(u.startPanel.hidden=!f),u.startToggleLabel&&(u.startToggleLabel.textContent=String(f?o.hideStartOptions||"Hide options":o.showStartOptions||"Show options"))},W=e=>{if(!(u.resultsHeading instanceof HTMLElement)||"undefined"==typeof window)return null;const t=u.resultsHeading.getBoundingClientRect().top;return Math.max(window.scrollY+t-(()=>{const e=document.getElementById("wpadminbar");return(e instanceof HTMLElement?e.offsetHeight:0)+16})()-e,0)},j=(e,t={})=>{if(!(u.startPanel instanceof HTMLElement&&u.startToggle))return f=e,void U();const a=u.startPanel,n=B?0:480,r="number"==typeof t.scrollToY?t.scrollToY:null;P&&(window.cancelAnimationFrame(P),P=0);const o=a.hidden?0:a.getBoundingClientRect().height;a.hidden=!1,e&&(a.style.height="");const s=e?a.scrollHeight:0,i=e&&s>0?Math.max(o/s,0):1,l=e?1:0;if(f=e,a.style.overflow="hidden",a.style.pointerEvents="none",a.style.height=`${o}px`,a.style.opacity=String(i),U({syncHidden:!1}),n<=0||o===s)return a.style.height="",a.style.overflow="",a.style.pointerEvents="",a.style.opacity="",a.hidden=!e,U(),void(null!==r&&"undefined"!=typeof window&&window.scrollTo({top:r,behavior:"auto"}));const d=performance.now(),c=t=>{const u=Math.min((t-d)/n,1),p=(y=u)<.5?4*y*y*y:1-Math.pow(-2*y+2,3)/2;var y;const g=o+(s-o)*p,m=i+(l-i)*p;a.style.height=`${Math.max(g,0)}px`,a.style.opacity=String(Math.max(Math.min(m,1),0)),u<1?P=window.requestAnimationFrame(c):(a.style.height="",a.style.overflow="",a.style.pointerEvents="",a.style.opacity="",a.hidden=!e,U(),null!==r&&"undefined"!=typeof window&&window.scrollTo({top:r,behavior:"auto"}),P=0)};P=window.requestAnimationFrame(c)},K=(e={})=>{if(!f)return;const t=u.startPanel instanceof HTMLElement?u.startPanel.getBoundingClientRect().height:0,a=e.scrollToResults?W(t):null;j(!1,{scrollToY:a})},O=()=>{"undefined"==typeof window||k||I||!f||"default"!==d.startMode||""!==String(d.customStart||"").trim()||window.scrollY-C<100||(I=!0,K())},Y=async(e,i,l={})=>{if(!c)return"unsupported";const g=Boolean(l.appendBrowseResults),m=String(l.announcementMessage||""),f=String(l.errorMessage||""),v=String(l.searchContextKey||""),R="browse"===e&&!1!==l.refreshRoute,k="route"===e?l.routeFocusRequest??null:null;if(_ instanceof AbortController){if("route"===H)return r(n,"info","request:blocked",{endpointKey:e,blockedBy:H}),"busy";_.abort()}$+=1;const I=$;"route"===e&&(x=k?{requestId:I,focusRequest:k}:null),_=new AbortController,H=e,E(t,!0),T(u,d,!0),A(t,"route"===e),q(t,"route"===e),r(n,"info","request:start",{endpointKey:e,payload:i});try{const l={...i,endpoint_token:n.rest.endpointToken};"browse"===e&&(l.refresh_route=R,""!==v&&(l.search_context_key=v));const c=await fetch(n.rest["browse"===e?"browseUrl":"routeUrl"],{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(l),signal:_.signal}),E=await c.json().catch(()=>({}));if(r(n,c.ok?"info":"warn","request:response",{endpointKey:e,status:c.status,ok:c.ok,body:E}),!c.ok)throw new Error(E?.message||o.requestFailed||"");if(I!==$)return!0;if("browse"===e){const e=d.category||"",n=d.expandedCategory||"",r=d.categorySearch||"",l=E?.browse||{};if(g&&""!==String(l.searchResultsError||""))return d.isLoadingMore=!1,h(u,d,o,{appendResults:!0,appendedResults:[],isLoadingMore:!1}),F(f||o.requestFailed||"",f||o.requestFailed||""),"failed";const c=g&&""!==v&&v===String(d.browse?.searchContextKey||"")&&v===String(l.searchContextKey||""),m=c?p(d.browse,l):[];if(d.browse=c?{...(C=d.browse)||{},...(B=l)||{},searchResults:[...Array.isArray(C?.searchResults)?C.searchResults:[],...p(C,B)]}:l,d.route=E?.route||d.route||{},d.category=String(d.browse.categoryKey||i.category||""),d.categorySearch=String(d.browse.categorySearch||i.category_search||""),d.isLoadingMore=!1,d.category?(d.expandedCategory=d.category===e?n:d.category,d.customResultsExpanded=!1):d.browse.hasSearch?String(i.category_search||"")!==r&&(d.expandedCategory="",d.customResultsExpanded=!0):(d.expandedCategory="",d.customResultsExpanded=!1),c)return h(u,d,o,{appendResults:!0,appendedResults:m,isLoadingMore:!1}),b(u,d,o),S(u,d),y(u,d),L(u,d),M(u,d),A(t,!1),s(u.liveRegion,d.browse?.searchResultsError?f||o.requestFailed||"":((e,t,n)=>e>0?a(n.loadedMoreResults||"",{count:e}):String(t?.hasMoreResults?n.resultsUpdated||"":n.noMoreResults||""))(m.length,d.browse,o)),"success"}else d.route=E?.route||d.route||{},d.category=String(d.route.categoryKey||d.category||""),d.categorySearch=String(d.route.categorySearch||i.category_search||"");return d.startMode=String(i.start_mode||d.startMode||"default"),d.customStart=String(i.custom_start||""),D(),"route"===e&&x&&x.requestId===I&&(((e,t)=>{if(!t)return;const a=String(t.placeId||""),n=e.tripHeaderActions?.querySelector("button:not([disabled]):not([hidden])");let r=null;if(a&&"add-waypoint"===t.action)r=e.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`);else if(a&&"move-waypoint"===t.action){const n=String(t.direction||"");r=e.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="move_waypoint"][value="${a}:${n}"]`)||e.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`)}else"remove-waypoint"===t.action?r=e.tripRegion?.querySelector('[data-plan-trip-list] button[name="remove_waypoint"]')||n:"clear-trip"===t.action&&(r=n||e.tripHeading);w(r)||w(e.tripHeading)})(u,x.focusRequest),x=null),s(u.liveRegion,m||""),"success"}catch(t){return"AbortError"===t?.name?(r(n,"info","request:aborted",{endpointKey:e}),"aborted"):(r(n,"error","request:failed",{endpointKey:e,error:t instanceof Error?t.message:String(t||""),payload:i}),g&&(d.isLoadingMore=!1,h(u,d,o,{appendResults:!0,appendedResults:[],isLoadingMore:!1})),F(g?f||o.requestFailed||"":t instanceof Error?t.message:o.requestFailed||"",g&&(f||o.requestFailed)||""),"failed")}finally{I===$&&(g&&d.isLoadingMore&&(d.isLoadingMore=!1),E(t,!1),T(u,d,!1),A(t,!1),"route"===e&&x&&x.requestId===I&&(x=null),q(t,!1),_=null,H="")}var C,B};if(u.startModeInputs.forEach(e=>{e.addEventListener("change",()=>{d.startMode=i(u.startModeInputs)||d.startMode||"default",L(u,d),k=!0,"custom"===d.startMode&&""===d.customStart.trim()||K({scrollToResults:!0}),c?Y("browse",l(u,d),{announcementMessage:o.startingPointUpdated||"",refreshRoute:!0}):s(u.liveRegion,o.startingPointUpdated||"")})}),u.customStartInput instanceof HTMLInputElement&&u.customStartInput.addEventListener("change",()=>{d.customStart=u.customStartInput.value||"",L(u,d),k=!0,"custom"===d.startMode&&""!==d.customStart.trim()&&K({scrollToResults:!0}),c?Y("browse",l(u,d),{announcementMessage:o.startingPointUpdated||"",refreshRoute:!0}):s(u.liveRegion,o.startingPointUpdated||"")}),"undefined"!=typeof window&&window.addEventListener("scroll",O,{passive:!0}),u.categorySearchInput instanceof HTMLInputElement&&(u.categorySearchInput.addEventListener("input",()=>{d.categorySearch=u.categorySearchInput.value||""}),u.categorySearchInput.addEventListener("keydown",e=>{if("Enter"!==e.key||!c)return;e.preventDefault();const t=l(u,d);t.category="",t.category_search=u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0,Y("browse",t,{announcementMessage:o.resultsUpdated||"",refreshRoute:R(d.route)})})),u.form instanceof HTMLFormElement&&u.form.addEventListener("submit",e=>{const t=e.submitter;if(!(t instanceof HTMLButtonElement&&c))return;let n="browse",r=o.resultsUpdated||"";const i=l(u,d);if(t.matches("[data-plan-category-button]")){const n=t.getAttribute("data-category-key")||"";if(n===d.category){const r=t.querySelector(".plan-your-day__category-title")?.textContent?.trim()||n;return e.preventDefault(),d.expandedCategory=d.expandedCategory===n?"":n,d.customResultsExpanded=!1,h(u,d,o),void s(u.liveRegion,d.expandedCategory===n?a(o.categoryResultsExpanded||"",{category:r}):a(o.categoryResultsCollapsed||"",{category:r}))}i.category=n,i.category_search="",d.expandedCategory=n,d.customResultsExpanded=!1}else t.matches('[data-plan-action="search-category-query"]')?(i.category="",i.category_search=u.categorySearchInput instanceof HTMLInputElement&&u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0):t.matches('[data-plan-action="add-waypoint"]')?(i.waypoints=[...i.waypoints,t.getAttribute("data-place-id")||t.value||""],n="route",r=o.tripUpdated||""):t.matches('[data-plan-action="remove-waypoint"]')?(i.remove_waypoint=t.getAttribute("data-place-id")||t.value||"",n="route",r=o.tripUpdated||""):t.matches("[data-plan-clear-trip]")?(i.clear_trip=!0,n="route",r=o.tripUpdated||""):"move_waypoint"===t.name&&t.value&&(i.move_waypoint=t.value,n="route",r=o.tripUpdated||"");e.preventDefault(),Y(n,i,{announcementMessage:r,refreshRoute:"browse"===n?R(d.route):void 0,routeFocusRequest:"route"===n?v(t):null})}),t.addEventListener("click",e=>{const t=e.target;if(!(t instanceof HTMLElement))return;if(t.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement)return e.preventDefault(),void s(u.liveRegion,o.openMapsDisabled||"");if(t.closest("[data-plan-start-toggle]")instanceof HTMLButtonElement)return e.preventDefault(),f?K():f||j(!0),void s(u.liveRegion,f?o.startOptionsExpanded||"":o.startOptionsCollapsed||"");if(t.closest("[data-plan-custom-results-button]")instanceof HTMLButtonElement){if(e.preventDefault(),!d.browse?.hasSearch||d.category)return;return d.expandedCategory="",d.customResultsExpanded=!d.customResultsExpanded,h(u,d,o),void s(u.liveRegion,d.customResultsExpanded?String(o.customResultsExpanded||""):String(o.customResultsCollapsed||""))}const a=t.closest("[data-plan-load-more-button]");if(a instanceof HTMLButtonElement){if(e.preventDefault(),!c||d.isLoadingMore||a.disabled)return;const t=String(d.browse?.nextPageToken||"");if(!t)return void s(u.liveRegion,o.noMoreResults||"");d.isLoadingMore=!0,h(u,d,o,{appendResults:!0,appendedResults:[],isLoadingMore:!0}),s(u.liveRegion,o.loadingMoreResults||""),Y("browse",{...l(u,d),page_token:t,append_results:!0},{appendBrowseResults:!0,errorMessage:o.loadMoreError||"",refreshRoute:!1,searchContextKey:String(d.browse?.searchContextKey||"")})}}),t.addEventListener("keydown",e=>{const t=e.target;if(!(t instanceof HTMLElement))return;t.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement&&("Enter"!==e.key&&" "!==e.key||(e.preventDefault(),s(u.liveRegion,o.openMapsDisabled||"")))}),D(),U(),t.classList.add("is-enhanced"),m){if(!c)return void F(o.requestFailed||"");Y("browse",l(u,d),{refreshRoute:!0})}},$=()=>{document.querySelectorAll("[data-plan-root]").forEach(H)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",$,{once:!0}):$()})(); \ No newline at end of file +(()=>{const t="planYourDayEnhanced",e="planYourDayColorMode",a="light",n="dark",r="system",o=[a,n],s=[...o,r],i="checking",l="found",u="not_found",d=[i,l,u],c=t=>String(t??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),p=(t,e={})=>{let a=String(t||"");return Object.entries(e||{}).forEach((([t,e])=>{a=a.split(`{${t}}`).join(String(e??""))})),a},y=(t,e="")=>Array.isArray(t)?t.map((t=>y(t))):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map((([t,e])=>{const a=String(t).toLowerCase();return[t,a.includes("token")||a.includes("api_key")||a.includes("authorization")||a.includes("cookie")||a.includes("secret")?"[redacted]":y(e,t)]}))):"string"==typeof t?String(e).toLowerCase().includes("token")?"[redacted]":t.replace(/([?&](?:key|api_key|token)=)[^&]+/gi,"$1[redacted]"):t,g=(t,e,a,n={})=>{if(!t?.debug||"undefined"==typeof console)return;("function"==typeof console[e]?console[e]:console.log).call(console,`[plan-your-day] ${a}`,y(n))},m=t=>Array.isArray(t)?t.map((t=>String(t??""))).filter(Boolean):[],S=(t,e)=>{t&&e&&(t.textContent="",window.requestAnimationFrame((()=>{t.textContent=e})))},f=t=>o.includes(String(t||"")),b=t=>{const e=String(t||"");return s.includes(e)?e:a},h=()=>{try{const t=window.localStorage?.getItem(e);return f(t)?t:""}catch(t){return""}},w=()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)"),v=(t,e=w())=>{const o=h();if(f(o))return o;const s=b(t);return s===r?e?.matches?n:a:s},M=(t,e,r,o)=>{const s=f(r)?r:a;t.setAttribute("data-plan-color-mode",s),((t,e,a)=>{t.colorModeToggle instanceof HTMLButtonElement&&(t.colorModeToggle.hidden=!1,t.colorModeToggle.setAttribute("aria-pressed",String(e===n)),t.colorModeToggle.setAttribute("aria-label",String(a.darkModeLabel||"Dark mode")),t.colorModeToggleLabel instanceof HTMLElement&&(t.colorModeToggleLabel.textContent=String(a.darkModeLabel||"Dark mode")))})(e,s,o)},L=t=>{const e=t.find((t=>t.checked));return e?e.value:""},R=t=>{const e=String(t||"");return d.includes(e)?e:""},E=(t,e)=>{const a=t.form instanceof HTMLFormElement?new FormData(t.form):new FormData;return{category:String(a.get("category")||e.category||""),category_search:String(a.get("category_search")||e.categorySearch||""),waypoints:a.getAll("waypoints[]").map((t=>String(t||""))).filter(Boolean),start_mode:String(a.get("start_mode")||e.startMode||"default"),custom_start:String(a.get("custom_start")||"")}},_=(t,e,a)=>{const n=String(t?.id||""),r=String(t?.label||""),o=String(t?.address||""),s=String(t?.distance_label||""),i=String(t?.maps_uri||""),l=e.includes(n);return`\n
  • \n
    \n

    ${c(r)}

    \n ${s?`

    ${c(s)}

    `:""}\n

    ${c(o)}

    \n
    \n
    \n ${i?`${c(a.viewInGoogleMaps||"")}`:""}\n ${l?`${c(a.inTrip||"")}`:``}\n
    \n
  • \n `},A=(t,e,a=!1)=>{if(!t?.hasMoreResults)return"";const n=String(a?e.loadingMoreResults||e.moreResultsButton||"":e.moreResultsButton||"");return`\n
    \n \n
    \n `},q=(t,e,a,n={})=>`\n
    \n ${((t,e,a)=>{const n=Array.isArray(t?.searchResults)?t.searchResults:[];if(0===n.length){const e=t?.resultsEmptyState||{};return`\n
    \n

    ${c(e.heading||"")}

    \n

    ${c(e.body||"")}

    \n
    \n `}return`\n \n `})(t,e,a)}\n
    \n ${A(t,a,Boolean(n.isLoadingMore))}\n `,T=(t,e)=>{const a=new Set((t=>{const e=[];return(Array.isArray(t?.searchResults)?t.searchResults:[]).forEach((t=>{const a=String(t?.id||"");a&&!e.includes(a)&&e.push(a)})),e})(t));return(Array.isArray(e?.searchResults)?e.searchResults:[]).filter((t=>{const e=String(t?.id||"");return!e||!a.has(e)&&(a.add(e),!0)}))},H=(t,e)=>{t.categoryInput&&(t.categoryInput.value=e.category||""),t.waypointInputs&&(t.waypointInputs.innerHTML=m(e.route?.selectedWaypointIds).map((t=>``)).join(""))},k=(t,e)=>{if(!t.messages)return;const a=Array.isArray(e)?e:[];t.messages.hidden=0===a.length,t.messages.innerHTML=(t=>(Array.isArray(t)?t:[]).map((t=>{const e=String(t?.type||"note"),a=String(t?.text||""),n="warning"===e?"alert":"";return`
  • ${c(a)}
  • `})).join(""))(a)},$=(t,e,a,n)=>{if(!(t instanceof HTMLElement))return;const r=t.querySelector("[data-plan-load-more-wrap]"),o=A(e,a,n);o?r instanceof HTMLElement?r.outerHTML=o:t.insertAdjacentHTML("beforeend",o):r instanceof HTMLElement&&r.remove()},x=(t,e,a,n,r={})=>{if(!(t instanceof HTMLElement))return;const o=Boolean(r.appendResults),s=Array.isArray(r.appendedResults)?r.appendedResults:[];o&&(s.length>0&&((t,e,a,n)=>{if(!(t instanceof HTMLElement&&Array.isArray(e)&&0!==e.length))return!1;const r=t.querySelector("[data-plan-results-list]");if(!(r instanceof HTMLElement))return!1;const o=t.querySelector("[data-plan-results-empty]");return o instanceof HTMLElement&&o.remove(),r.insertAdjacentHTML("beforeend",e.map((t=>_(t,a,n))).join("")),!0})(t,s,a,n)||0===s.length&&(t=>t instanceof HTMLElement&&t.querySelector("[data-plan-results-list], [data-plan-results-empty]")instanceof HTMLElement)(t))?$(t,e,n,Boolean(r.isLoadingMore)):t.innerHTML=q(e,a,n,r)},C=(t,e,a,n={})=>{const r=e.category||"",o=e.expandedCategory||"",s=m(e.route?.selectedWaypointIds),i=e.browse||{},l=Boolean(i.hasSearch)&&!r,u=l||0===t.categoryButtons.length,d=l&&Boolean(e.customResultsExpanded)||!l&&0===t.categoryButtons.length,c={appendResults:Boolean(n.appendResults),appendedResults:Array.isArray(n.appendedResults)?n.appendedResults:[],isLoadingMore:Boolean(n.isLoadingMore)};t.categoryButtons.forEach((t=>{const e=t.getAttribute("data-category-key")||"",a=e===r&&e===o,n=t.closest(".plan-your-day__category-accordion-item");t.setAttribute("aria-expanded",String(a)),n instanceof HTMLElement&&n.classList.toggle("is-expanded",a)})),t.categoryRegions.forEach((t=>{const e=t.getAttribute("data-category-key")||"",n=e===r&&e===o,l=t.querySelector("[data-plan-category-results-panel]");if(t.hidden=!n,l instanceof HTMLElement){if(!n)return void(l.innerHTML="");x(l,i,s,a,c)}})),t.customResults&&(t.customResults.hidden=!u,t.customResults.classList.toggle("is-expanded",d)),t.customResultsButton&&t.customResultsButton.setAttribute("aria-expanded",String(d)),t.customResultsRegion&&(t.customResultsRegion.hidden=!d),t.customResultsHeading&&(t.customResultsHeading.textContent=l?p(a.searchResultsFor||"",{search:i.categoryLabel||""}):String(i?.resultsEmptyState?.heading||"")),t.customResultsDescription&&(t.customResultsDescription.textContent=String(l?a.customSearchResultsDescription||"":i?.resultsEmptyState?.body||"")),t.customResultsPanel&&(d?l?x(t.customResultsPanel,i,s,a,c):t.customResultsPanel.innerHTML=q(i,s,a,{isLoadingMore:!1}):t.customResultsPanel.innerHTML="")},I=(t,e,a)=>{t.tripHeaderActions&&(t.tripHeaderActions.innerHTML=((t,e)=>{const a=m(t?.selectedWaypointIds),n=String(t?.tripCountLabel||"");return`\n ${c(n)}\n ${a.length>0?``:""}\n `})(e.route,a)),t.tripRegion&&(t.tripRegion.innerHTML=((t,e,a)=>{const n=Array.isArray(t?.tripWaypoints)?t.tripWaypoints:[];if(0===n.length){const a=t?.tripEmptyState||{};return`\n
    \n

    ${c(a.heading||e.tripEmptyHeading||"")}

    \n

    ${c(a.body||e.tripEmptyBody||"")}

    \n
    \n `}return`\n
      \n ${n.map(((t,a)=>{const r=String(t?.id||""),o=String(t?.label||""),s=String(t?.address||""),i=a>0,l=a\n
      \n \n
      \n

      ${c(o)}

      \n

      ${c(s)}

      \n
      \n
      \n
      \n \n ${c(e.moveUp||"")}\n \n \n ${c(e.moveDown||"")}\n \n \n
      \n \n `})).join("")}\n
    \n `})(e.route,a,t.tripRegion.getAttribute("data-plan-trip-help-id")||""))},B=(t,e,a)=>{const n=e.route||{},r=String(n.iframeSrc||""),o=n.emptyPreviewState||{},s=String(n.mapsUrl||""),i=m(n.selectedWaypointIds).length>0;k(t,n.messages),t.mapWrap&&(t.mapWrap.hidden=""===r),t.iframe&&(t.iframe.src=r),t.previewEmpty&&(t.previewEmpty.hidden=""!==r),t.previewEmptyHeading&&(t.previewEmptyHeading.textContent=String(o.heading||"")),t.previewEmptyBody&&(t.previewEmptyBody.textContent=String(o.body||"")),t.summaryCount&&(t.summaryCount.textContent=String(n.tripCountLabel||""),t.summaryCount.hidden=i),t.openLinkLabel&&(t.openLinkLabel.textContent=String(n.mapsLinkLabel||"")),t.openLink&&(t.openLink.hidden=!i,t.openLink.classList.toggle("is-disabled",""===s),s?(t.openLink.href=s,t.openLink.removeAttribute("aria-disabled"),t.openLink.removeAttribute("tabindex"),t.openLink.removeAttribute("role")):(t.openLink.removeAttribute("href"),t.openLink.setAttribute("aria-disabled","true"),t.openLink.setAttribute("tabindex","0"),t.openLink.setAttribute("role","button")))},D=t=>t instanceof HTMLElement&&"function"==typeof t.focus&&(t.focus(),document.activeElement===t),F=t=>{if(!(t instanceof HTMLButtonElement))return null;if(t.matches('[data-plan-action="add-waypoint"]'))return{action:"add-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches('[data-plan-action="remove-waypoint"]'))return{action:"remove-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches("[data-plan-clear-trip]"))return{action:"clear-trip",placeId:""};if("move_waypoint"===t.name&&t.value){const[e,a]=String(t.value).split(":",2);return{action:"move-waypoint",placeId:e||"",direction:a||""}}return null},P=t=>0===m(t?.selectedWaypointIds).length,U=(t,e,a)=>{const n=R(e);t.customStartWrap instanceof HTMLElement&&t.customStartWrap.setAttribute("data-plan-custom-start-state",n),t.customStartStatus instanceof HTMLElement&&(t.customStartStatus.textContent=((t,e)=>t===i?String(e.customStartChecking||"Checking starting address."):t===l?String(e.customStartFound||"Starting address found. Results are ready."):t===u?String(e.customStartNotFound||"Starting address was not found."):"")(n,a))},j=(t,e,a)=>{t.startModeInputs.forEach((t=>{t.checked=t.value===e.startMode})),t.customStartInput&&(t.customStartInput.value=e.customStart||"");const n="custom"===(L(t.startModeInputs)||e.startMode||"default");t.customStartWrap&&(t.customStartWrap.hidden=!n),t.customStartInput&&(t.customStartInput.disabled=!n),U(t,n?e.customStartStatus:"",a)},W=(t,e)=>{if(!(t.categorySearchInput instanceof HTMLInputElement))return;const a=String(e.categorySearch||"");t.categorySearchInput.value!==a&&(t.categorySearchInput.value=a)},O=(t,e)=>{t.classList.toggle("is-submitting",e),t.setAttribute("aria-busy",String(e))},K=(t,e,a)=>{t.forEach((t=>{if(!(t instanceof HTMLButtonElement||t instanceof HTMLInputElement))return;if(e)return t.hasAttribute(a)||t.setAttribute(a,t.disabled?"true":"false"),void(t.disabled=!0);const n=t.getAttribute(a);null!==n&&(t.disabled="true"===n,t.removeAttribute(a))}))},N=(t,e)=>{K(t.querySelectorAll("[data-plan-route-mutation]"),e,"data-plan-disabled-before-request")},J=(t,e)=>{K(t.querySelectorAll(['[data-plan-form] button[type="submit"]:not([data-plan-route-mutation])',"[data-plan-load-more-button]","[data-plan-start-toggle]","[data-plan-custom-results-button]",'input[name="start_mode"]',"[data-plan-custom-start]","[data-plan-category-search]"].join(",")),e,"data-plan-browse-disabled-before-request")},G=(t,e,a)=>{const n=e.category||"";if(t.categoryPanels.forEach((e=>{const r=e.getAttribute("data-category-key")||"",o=0===t.categoryButtons.length||r===n||!1===t.customResults?.hidden;e.setAttribute("aria-busy",String(a&&o))})),t.customResultsPanel instanceof HTMLElement){const e=!1===t.customResults?.hidden;t.customResultsPanel.setAttribute("aria-busy",String(a&&e))}t.tripRegion instanceof HTMLElement&&t.tripRegion.setAttribute("aria-busy",String(a)),t.previewCard instanceof HTMLElement&&t.previewCard.setAttribute("aria-busy",String(a))},Y=o=>{if(!(o instanceof HTMLElement)||"true"===o.dataset[t])return;o.dataset[t]="true";const s=(t=>{const e=t.querySelector("[data-plan-config]");if(!e)return{};try{return JSON.parse(e.textContent||"{}")}catch(t){return{}}})(o),l=s.strings||{},u={form:o.querySelector("[data-plan-form]"),liveRegion:o.querySelector("[data-plan-live-region]"),categoryInput:o.querySelector("[data-plan-category-input]"),waypointInputs:o.querySelector("[data-plan-waypoint-inputs]"),categoryButtons:Array.from(o.querySelectorAll("[data-plan-category-button]")),categoryItems:Array.from(o.querySelectorAll("[data-plan-category-item]")),categoryRegions:Array.from(o.querySelectorAll("[data-plan-category-region]")),categoryPanels:Array.from(o.querySelectorAll("[data-plan-category-results-panel]")),categorySearchInput:o.querySelector("[data-plan-category-search]"),customResults:o.querySelector("[data-plan-custom-results]"),customResultsButton:o.querySelector("[data-plan-custom-results-button]"),customResultsHeading:o.querySelector("[data-plan-custom-results-heading]"),customResultsDescription:o.querySelector("[data-plan-custom-results-description]"),customResultsRegion:o.querySelector("[data-plan-custom-results-region]"),customResultsPanel:o.querySelector("[data-plan-custom-results-panel]"),resultsHeading:o.querySelector("[data-plan-results-heading]"),startModeInputs:Array.from(o.querySelectorAll('input[name="start_mode"]')),customStartWrap:o.querySelector("[data-plan-custom-start-wrap]"),customStartInput:o.querySelector("[data-plan-custom-start]"),customStartStatus:o.querySelector("[data-plan-custom-start-status]"),startToggle:o.querySelector("[data-plan-start-toggle]"),startToggleLabel:o.querySelector("[data-plan-start-toggle-label]"),startPanel:o.querySelector("[data-plan-start-panel]"),colorModeToggle:o.querySelector("[data-plan-color-mode-toggle]"),colorModeToggleLabel:o.querySelector("[data-plan-color-mode-toggle-label]"),tripHeaderActions:o.querySelector("[data-plan-trip-header-actions]"),tripHeading:o.querySelector("[data-plan-trip-heading]"),tripRegion:o.querySelector("[data-plan-trip-region]"),messages:o.querySelector("[data-plan-messages]"),previewCard:o.querySelector("[data-plan-preview-card]"),mapWrap:o.querySelector("[data-plan-map-wrap]"),iframe:o.querySelector("[data-plan-iframe]"),previewEmpty:o.querySelector("[data-plan-preview-empty]"),previewEmptyHeading:o.querySelector("[data-plan-preview-empty-heading]"),previewEmptyBody:o.querySelector("[data-plan-preview-empty-body]"),summaryCount:o.querySelector("[data-plan-summary-count]"),openLink:o.querySelector("[data-plan-open-link]"),openLinkLabel:o.querySelector("[data-plan-open-link-label]")},d={category:String(s.initialState?.category||""),categorySearch:String(s.initialState?.categorySearch||""),startMode:String(s.initialState?.startMode||"default"),customStart:String(s.initialState?.customStart||""),customStartStatus:R(s.initialData?.browse?.customStartStatus||""),expandedCategory:String(s.initialState?.category||""),customResultsExpanded:Boolean(s.initialData?.browse?.isCustomSearch),isLoadingMore:!1,browse:s.initialData?.browse||{},route:s.initialData?.route||{}},c="string"==typeof s.rest?.bootstrapUrl&&""!==s.rest.bootstrapUrl;let y="string"==typeof s.rest?.endpointToken?s.rest.endpointToken:"",m=!1,_=null;const A=u.form instanceof HTMLFormElement&&"string"==typeof s.rest?.browseUrl&&""!==s.rest.browseUrl&&"string"==typeof s.rest?.routeUrl&&""!==s.rest.routeUrl&&(""!==y||c),q=Boolean(s.hydration?.shouldHydrateOnLoad),$=b(s.colorModeDefault||o.getAttribute("data-plan-color-mode-default")),x=w();let U=!0,K=null,Y="",z=0,Q=null;const V="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;let X=0;M(o,u,v($,x),l),$===r&&x&&x.addEventListener("change",(()=>{h()||M(o,u,v($,x),l)}));const Z=()=>{C(u,d,l,{isLoadingMore:d.isLoadingMore}),I(u,d,l),B(u,d),H(u,d),j(u,d,l),W(u,d),N(o,!1)},tt=(t,e="")=>{k(u,[{type:"warning",text:t||l.requestFailed||""}]),S(u.liveRegion,e||t||l.requestFailed||"")},et=(t={})=>{if(!u.startToggle||!u.startPanel)return;const e=!1!==t.syncHidden;u.startToggle.hidden=!1,u.startToggle.setAttribute("aria-expanded",String(U)),u.startToggle.classList.toggle("is-collapsed",!U),e&&(u.startPanel.hidden=!U),u.startToggleLabel&&(u.startToggleLabel.textContent=String(U?l.hideStartOptions||"Hide options":l.showStartOptions||"Show options"))},at=t=>{if(!(u.startPanel instanceof HTMLElement&&u.startToggle))return U=t,void et();const e=u.startPanel,a=V?0:480;X&&(window.cancelAnimationFrame(X),X=0);const n=e.hidden?0:e.getBoundingClientRect().height;e.hidden=!1,t&&(e.style.height="");const r=t?e.scrollHeight:0,o=t&&r>0?Math.max(n/r,0):1,s=t?1:0;if(U=t,e.style.overflow="hidden",e.style.pointerEvents="none",e.style.height=`${n}px`,e.style.opacity=String(o),et({syncHidden:!1}),a<=0||n===r)return e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,void et();const i=performance.now(),l=u=>{const d=Math.min((u-i)/a,1),c=(p=d)<.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;var p;const y=n+(r-n)*c,g=o+(s-o)*c;e.style.height=`${Math.max(y,0)}px`,e.style.opacity=String(Math.max(Math.min(g,1),0)),d<1?X=window.requestAnimationFrame(l):(e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,et(),X=0)};X=window.requestAnimationFrame(l)},nt=async(t,e,a={})=>{if(!A)return"unsupported";const n=Boolean(a.appendBrowseResults),r=String(a.announcementMessage||""),f=String(a.errorMessage||""),b=String(a.searchContextKey||""),h="browse"===t&&!1!==a.refreshRoute,w="route"===t?a.routeFocusRequest??null:null,v=String(e.start_mode||""),M=String(e.custom_start||""),L="browse"===t&&!n&&"custom"===v&&""!==M.trim(),E="route"===t&&(d.customStartStatus===i||"custom"!==v||""===M.trim()||M!==String(d.customStart||""));if(K instanceof AbortController){if("route"===Y)return g(s,"info","request:blocked",{endpointKey:t,blockedBy:Y}),"busy";K.abort()}z+=1;const q=z;"route"===t&&(Q=w?{requestId:q,focusRequest:w}:null),K=new AbortController,Y=t,O(o,!0),G(u,d,!0),N(o,"route"===t),J(o,"route"===t),L?(d.customStartStatus=i,j(u,d,l)):("browse"===t&&!n||E)&&(d.customStartStatus="",j(u,d,l)),g(s,"info","request:start",{endpointKey:t,payload:e});try{const a=await(async()=>c?m&&""!==y?y:_||(_=fetch(s.rest.bootstrapUrl,{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({})}).then((async t=>{const e=await t.json().catch((()=>({})));if(g(s,t.ok?"info":"warn","request:bootstrap",{status:t.status,ok:t.ok,body:e}),!t.ok)throw new Error(e?.message||l.requestFailed||"");const a=String(e?.endpointToken||"");if(""===a)throw new Error(l.requestFailed||"");return y=a,m=!0,y})).finally((()=>{_=null})),_):y)();if(""===a)throw new Error(l.requestFailed||"");const i={...e,endpoint_token:a};"browse"===t&&(i.refresh_route=h,""!==b&&(i.search_context_key=b));const w=await fetch(s.rest["browse"===t?"browseUrl":"routeUrl"],{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(i),signal:K.signal}),v=await w.json().catch((()=>({})));if(g(s,w.ok?"info":"warn","request:response",{endpointKey:t,status:w.status,ok:w.ok,body:v}),!w.ok)throw new Error(v?.message||l.requestFailed||"");if(q!==z)return!0;if("browse"===t){const t=d.category||"",a=d.expandedCategory||"",r=d.categorySearch||"",s=v?.browse||{};if(n&&""!==String(s.searchResultsError||""))return d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1}),tt(f||l.requestFailed||"",f||l.requestFailed||""),"failed";const i=n&&""!==b&&b===String(d.browse?.searchContextKey||"")&&b===String(s.searchContextKey||""),c=i?T(d.browse,s):[];if(d.browse=i?{...(k=d.browse)||{},...($=s)||{},searchResults:[...Array.isArray(k?.searchResults)?k.searchResults:[],...T(k,$)]}:s,d.route=v?.route||d.route||{},d.category=String(d.browse.categoryKey||e.category||""),d.categorySearch=String(d.browse.categorySearch||e.category_search||""),d.customStartStatus=R(d.browse.customStartStatus||""),d.isLoadingMore=!1,d.category?(d.expandedCategory=d.category===t?a:d.category,d.customResultsExpanded=!1):d.browse.hasSearch?String(e.category_search||"")!==r&&(d.expandedCategory="",d.customResultsExpanded=!0):(d.expandedCategory="",d.customResultsExpanded=!1),i)return C(u,d,l,{appendResults:!0,appendedResults:c,isLoadingMore:!1}),I(u,d,l),B(u,d),H(u,d),j(u,d,l),W(u,d),N(o,!1),S(u.liveRegion,d.browse?.searchResultsError?f||l.requestFailed||"":((t,e,a)=>t>0?p(a.loadedMoreResults||"",{count:t}):String(e?.hasMoreResults?a.resultsUpdated||"":a.noMoreResults||""))(c.length,d.browse,l)),"success"}else d.route=v?.route||d.route||{},d.category=String(d.route.categoryKey||d.category||""),d.categorySearch=String(d.route.categorySearch||e.category_search||"");return d.startMode=String(e.start_mode||d.startMode||"default"),d.customStart=String(e.custom_start||""),Z(),"route"===t&&Q&&Q.requestId===q&&(((t,e)=>{if(!e)return;const a=String(e.placeId||""),n=t.tripHeaderActions?.querySelector("button:not([disabled]):not([hidden])");let r=null;if(a&&"add-waypoint"===e.action)r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`);else if(a&&"move-waypoint"===e.action){const n=String(e.direction||"");r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="move_waypoint"][value="${a}:${n}"]`)||t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`)}else"remove-waypoint"===e.action?r=t.tripRegion?.querySelector('[data-plan-trip-list] button[name="remove_waypoint"]')||n:"clear-trip"===e.action&&(r=n||t.tripHeading);D(r)||D(t.tripHeading)})(u,Q.focusRequest),Q=null),S(u.liveRegion,r||""),"success"}catch(a){return"AbortError"===a?.name?(g(s,"info","request:aborted",{endpointKey:t}),q===z&&L&&(d.customStartStatus="",j(u,d,l)),"aborted"):q!==z?"stale":(g(s,"error","request:failed",{endpointKey:t,error:a instanceof Error?a.message:String(a||""),payload:e}),n&&(d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1})),tt(n?f||l.requestFailed||"":a instanceof Error?a.message:l.requestFailed||"",n&&(f||l.requestFailed)||""),L&&(d.customStartStatus="",j(u,d,l)),"failed")}finally{q===z&&(n&&d.isLoadingMore&&(d.isLoadingMore=!1),O(o,!1),G(u,d,!1),N(o,!1),"route"===t&&Q&&Q.requestId===q&&(Q=null),J(o,!1),K=null,Y="")}var k,$};if(u.startModeInputs.forEach((t=>{t.addEventListener("change",(()=>{d.startMode=L(u.startModeInputs)||d.startMode||"default",j(u,d,l),A?nt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")}))})),u.customStartInput instanceof HTMLInputElement&&u.customStartInput.addEventListener("change",(()=>{d.customStart=u.customStartInput.value||"",j(u,d,l),A?nt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")})),u.categorySearchInput instanceof HTMLInputElement&&(u.categorySearchInput.addEventListener("input",(()=>{d.categorySearch=u.categorySearchInput.value||""})),u.categorySearchInput.addEventListener("keydown",(t=>{if("Enter"!==t.key||!A)return;t.preventDefault();const e=E(u,d);e.category="",e.category_search=u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0,nt("browse",e,{announcementMessage:l.resultsUpdated||"",refreshRoute:P(d.route)})}))),u.form instanceof HTMLFormElement&&u.form.addEventListener("submit",(t=>{const e=t.submitter;if(!(e instanceof HTMLButtonElement&&A))return;let a="browse",n=l.resultsUpdated||"";const r=E(u,d);if(e.matches("[data-plan-category-button]")){const a=e.getAttribute("data-category-key")||"";if(a===d.category){const n=e.querySelector(".plan-your-day__category-title")?.textContent?.trim()||a;return t.preventDefault(),d.expandedCategory=d.expandedCategory===a?"":a,d.customResultsExpanded=!1,C(u,d,l),void S(u.liveRegion,d.expandedCategory===a?p(l.categoryResultsExpanded||"",{category:n}):p(l.categoryResultsCollapsed||"",{category:n}))}r.category=a,r.category_search="",d.expandedCategory=a,d.customResultsExpanded=!1}else e.matches('[data-plan-action="search-category-query"]')?(r.category="",r.category_search=u.categorySearchInput instanceof HTMLInputElement&&u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0):e.matches('[data-plan-action="add-waypoint"]')?(r.waypoints=[...r.waypoints,e.getAttribute("data-place-id")||e.value||""],a="route",n=l.tripUpdated||""):e.matches('[data-plan-action="remove-waypoint"]')?(r.remove_waypoint=e.getAttribute("data-place-id")||e.value||"",a="route",n=l.tripUpdated||""):e.matches("[data-plan-clear-trip]")?(r.clear_trip=!0,a="route",n=l.tripUpdated||""):"move_waypoint"===e.name&&e.value&&(r.move_waypoint=e.value,a="route",n=l.tripUpdated||"");t.preventDefault(),nt(a,r,{announcementMessage:n,refreshRoute:"browse"===a?P(d.route):void 0,routeFocusRequest:"route"===a?F(e):null})})),o.addEventListener("click",(t=>{const r=t.target;if(!(r instanceof HTMLElement))return;if(r.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement)return t.preventDefault(),void S(u.liveRegion,l.openMapsDisabled||"");if(r.closest("[data-plan-start-toggle]")instanceof HTMLButtonElement)return t.preventDefault(),U?U&&at(!1):U||at(!0),void S(u.liveRegion,U?l.startOptionsExpanded||"":l.startOptionsCollapsed||"");if(r.closest("[data-plan-color-mode-toggle]")instanceof HTMLButtonElement){t.preventDefault();const r=(o.getAttribute("data-plan-color-mode")===n?n:a)===n?a:n;return(t=>{if(f(t))try{window.localStorage?.setItem(e,t)}catch(t){}})(r),void M(o,u,r,l)}if(r.closest("[data-plan-custom-results-button]")instanceof HTMLButtonElement){if(t.preventDefault(),!d.browse?.hasSearch||d.category)return;return d.expandedCategory="",d.customResultsExpanded=!d.customResultsExpanded,C(u,d,l),void S(u.liveRegion,d.customResultsExpanded?String(l.customResultsExpanded||""):String(l.customResultsCollapsed||""))}const s=r.closest("[data-plan-load-more-button]");if(s instanceof HTMLButtonElement){if(t.preventDefault(),!A||d.isLoadingMore||s.disabled)return;const e=String(d.browse?.nextPageToken||"");if(!e)return void S(u.liveRegion,l.noMoreResults||"");d.isLoadingMore=!0,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!0}),S(u.liveRegion,l.loadingMoreResults||""),nt("browse",{...E(u,d),page_token:e,append_results:!0},{appendBrowseResults:!0,errorMessage:l.loadMoreError||"",refreshRoute:!1,searchContextKey:String(d.browse?.searchContextKey||"")})}})),o.addEventListener("keydown",(t=>{const e=t.target;if(!(e instanceof HTMLElement))return;e.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement&&("Enter"!==t.key&&" "!==t.key||(t.preventDefault(),S(u.liveRegion,l.openMapsDisabled||"")))})),Z(),et(),o.classList.add("is-enhanced"),q){if(!A)return void tt(l.requestFailed||"");nt("browse",E(u,d),{refreshRoute:!0})}},z=()=>{document.querySelectorAll("[data-plan-root]").forEach(Y)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",z,{once:!0}):z()})(); \ No newline at end of file diff --git a/plugin/plan-your-day/blocks/planner/block.json b/plugin/plan-your-day/blocks/planner/block.json index 2e1b63c..73b58b0 100644 --- a/plugin/plan-your-day/blocks/planner/block.json +++ b/plugin/plan-your-day/blocks/planner/block.json @@ -2,10 +2,9 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "plan-your-day/planner", - "title": "Plan Your Day", + "title": "Waypoints", "category": "widgets", - "icon": "location-alt", - "description": "Render the Plan Your Day planner through the block editor.", + "description": "Render the Waypoints planner through the block editor.", "textdomain": "plan-your-day", "keywords": [ "planner", diff --git a/plugin/plan-your-day/composer.json b/plugin/plan-your-day/composer.json index 487ea45..953571f 100644 --- a/plugin/plan-your-day/composer.json +++ b/plugin/plan-your-day/composer.json @@ -8,6 +8,8 @@ }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "php-stubs/wordpress-stubs": "~6.8.0", + "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^10.5", "squizlabs/php_codesniffer": "^3.10", "wp-coding-standards/wpcs": "^3.1" @@ -25,6 +27,7 @@ "scripts": { "lint": "./tools/php-runner.sh tools/lint.php", "phpcs": "./tools/php-runner.sh vendor/bin/phpcs -n --standard=phpcs.xml.dist", + "phpstan": "./tools/phpstan.sh", "phpunit": "./tools/php-runner.sh vendor/bin/phpunit --configuration phpunit.xml.dist", "build-release": "./tools/build-release-zip.sh", "test": [ diff --git a/plugin/plan-your-day/composer.lock b/plugin/plan-your-day/composer.lock index e6b3992..7133c8a 100644 --- a/plugin/plan-your-day/composer.lock +++ b/plugin/plan-your-day/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "06610815417192d5d5e674a06259fd1f", + "content-hash": "8a398f70242e673ebb3e69e04e537796", "packages": [], "packages-dev": [ { @@ -339,6 +339,57 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.8.3", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "abeb5a8b58fda7ac21f15ee596f302f2959a7114" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/abeb5a8b58fda7ac21f15ee596f302f2959a7114", + "reference": "abeb5a8b58fda7ac21f15ee596f302f2959a7114", + "shasum": "" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^5.5", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^5.4.1", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.3" + }, + "time": "2025-09-30T20:58:47+00:00" + }, { "name": "phpcsstandards/phpcsextra", "version": "1.5.0", @@ -514,6 +565,70 @@ ], "time": "2025-12-08T14:27:58+00:00" }, + { + "name": "phpstan/phpstan", + "version": "2.2.2", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e5cc34d491a90e79c216d824f60fe21fd4d93bd6", + "reference": "e5cc34d491a90e79c216d824f60fe21fd4d93bd6", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-06-05T09:00:01+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "10.1.16", @@ -2095,12 +2210,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.2" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/plugin/plan-your-day/docs/phpstan.md b/plugin/plan-your-day/docs/phpstan.md new file mode 100644 index 0000000..18661d8 --- /dev/null +++ b/plugin/plan-your-day/docs/phpstan.md @@ -0,0 +1,20 @@ +# PHPStan + +This plugin uses PHPStan as a development-only Composer dependency. + +Install dependencies: + +```bash +composer install +``` + +Run PHPStan from the plugin directory: + +```bash +composer run phpstan +``` + +The config scans `plan-your-day.php`, `uninstall.php`, and `src/`. WordPress +symbols are provided by `php-stubs/wordpress-stubs`, and plugin constants are +defined in `tools/phpstan/bootstrap.php` so the scan does not require a running +WordPress install. diff --git a/plugin/plan-your-day/phpcs.xml.dist b/plugin/plan-your-day/phpcs.xml.dist index edba4fb..033db34 100644 --- a/plugin/plan-your-day/phpcs.xml.dist +++ b/plugin/plan-your-day/phpcs.xml.dist @@ -1,6 +1,6 @@ - - WordPress Coding Standards checks for Plan Your Day plugin code. + + WordPress Coding Standards checks for Waypoints plugin code. diff --git a/plugin/plan-your-day/phpstan.neon b/plugin/plan-your-day/phpstan.neon new file mode 100644 index 0000000..414e659 --- /dev/null +++ b/plugin/plan-your-day/phpstan.neon @@ -0,0 +1,10 @@ +parameters: + level: 3 + paths: + - plan-your-day.php + - uninstall.php + - src + bootstrapFiles: + - tools/phpstan/bootstrap.php + scanFiles: + - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php diff --git a/plugin/plan-your-day/phpunit.xml.dist b/plugin/plan-your-day/phpunit.xml.dist index 89cab3c..38c58d8 100644 --- a/plugin/plan-your-day/phpunit.xml.dist +++ b/plugin/plan-your-day/phpunit.xml.dist @@ -7,7 +7,7 @@ failOnWarning="true" > - + tests diff --git a/plugin/plan-your-day/plan-your-day.php b/plugin/plan-your-day/plan-your-day.php index e399c72..3522366 100644 --- a/plugin/plan-your-day/plan-your-day.php +++ b/plugin/plan-your-day/plan-your-day.php @@ -1,8 +1,8 @@ init(); function plan_your_day_missing_autoloader_message(): string { - return 'Plan Your Day is missing Composer dependencies. Install a built release zip that includes vendor/autoload.php, ' + return 'Waypoints is missing Composer dependencies. Install a built release zip that includes vendor/autoload.php, ' . 'or run composer install inside the plugin directory for a source checkout.'; } @@ -48,7 +48,7 @@ function plan_your_day_missing_autoloader_activation(): void { wp_die( esc_html( plan_your_day_missing_autoloader_message() ), - esc_html( 'Plan Your Day activation failed' ), + esc_html( 'Waypoints activation failed' ), [ 'back_link' => true ] ); } diff --git a/plugin/plan-your-day/readme.txt b/plugin/plan-your-day/readme.txt index ac71836..5759cef 100644 --- a/plugin/plan-your-day/readme.txt +++ b/plugin/plan-your-day/readme.txt @@ -1,10 +1,10 @@ -=== Plan Your Day === +=== Waypoints === Contributors: acodebeard Tags: planning, maps, wayfinding Requires at least: 6.8 -Tested up to: 6.9 +Tested up to: 7.0 Requires PHP: 8.2 -Stable tag: 0.1.0 +Stable tag: 0.5 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -12,7 +12,7 @@ A configurable day planning plugin for WordPress. == Description == -Plan Your Day is a configurable day planning plugin for WordPress. +Waypoints is a configurable day planning plugin for WordPress. Current development builds include the plugin scaffold, Settings API registration, admin settings screen, editable categories and interface copy, @@ -34,13 +34,13 @@ Source checkout: 2. Run `composer install` inside the plugin directory to generate `vendor/autoload.php`. 3. Activate the plugin through the Plugins screen in WordPress. -4. Open Settings > Plan Your Day and configure the required default location +4. Open Settings > Waypoints and configure the required default location and Google API keys. == Configuration == Settings are stored in the `plan_your_day_settings` option and managed through -Settings > Plan Your Day. +Settings > Waypoints. Current settings include: @@ -55,7 +55,7 @@ Current settings include: == External services == -Plan Your Day uses Google services to load place results, place details, +Waypoints uses Google services to load place results, place details, geocoding data, embedded map previews, and Google Maps handoff links. The plugin can send data to Google from the server when: @@ -93,7 +93,7 @@ still in progress. = How do I display the planner? = -Use the Plan Your Day block in the block editor or add `[plan_your_day]` to a +Use the Waypoints block in the block editor or add `[plan_your_day]` to a page, post, or Shortcode block. The repository docs cover placement guidance and the optional `action_url` / `Action URL` setting. @@ -104,11 +104,13 @@ architecture, settings, security, and troubleshooting notes. == Changelog == -= Unreleased = += 0.5 = * Added block-editor and shortcode entry points that share the same planner renderer. * Added admin-editable categories and interface copy settings. * Added REST-powered planner interactions, rate limiting, and Google API admin tools. * Added a reproducible release zip builder and manual release-process documentation. +* Added color mode controls, Noto Sans assets, and a cleaned-up settings interface. +* Added an upgrade cleanup that prunes removed interface-copy settings from existing installs. = 0.1.0 = * Initial plugin scaffold (GH issue #20): directory structure, main plugin file, activation / deactivation hooks, uninstall routine, PSR-4 autoloading. diff --git a/plugin/plan-your-day/release.json b/plugin/plan-your-day/release.json index b2e437c..905b87b 100644 --- a/plugin/plan-your-day/release.json +++ b/plugin/plan-your-day/release.json @@ -1,9 +1,9 @@ { - "name": "Plan Your Day", + "name": "Waypoints", "slug": "plan-your-day", - "version": "0.1.0", - "schemaVersion": 2, - "artifact": "../../dist/plan-your-day-0.1.0.zip", + "version": "0.5", + "schemaVersion": 5, + "artifact": "../../dist/plan-your-day-0.5.zip", "distribution": "github-release-zip", "notes": "Installable WordPress admin zip built from this source repository with Composer production autoload files included." } diff --git a/plugin/plan-your-day/src/Activator.php b/plugin/plan-your-day/src/Activator.php index ef44888..8f463db 100644 --- a/plugin/plan-your-day/src/Activator.php +++ b/plugin/plan-your-day/src/Activator.php @@ -11,7 +11,17 @@ final class Activator { public static function activate(): void { $settings = new Settings(); - add_option( Settings::OPTION_NAME, Settings::defaults(), '', false ); + add_option( + Settings::OPTION_NAME, + array_merge( + Settings::defaults(), + [ + 'categories' => Settings::default_categories(), + ] + ), + '', + false + ); $settings->seed_default_categories_if_needed(); update_option( 'plan_your_day_version', PLAN_YOUR_DAY_VERSION ); diff --git a/plugin/plan-your-day/src/Admin/SettingsPage.php b/plugin/plan-your-day/src/Admin/SettingsPage.php index bd212a3..1969dde 100644 --- a/plugin/plan-your-day/src/Admin/SettingsPage.php +++ b/plugin/plan-your-day/src/Admin/SettingsPage.php @@ -33,8 +33,8 @@ public function __construct( public function register(): void { add_options_page( - __( 'Plan Your Day Settings', 'plan-your-day' ), - __( 'Plan Your Day', 'plan-your-day' ), + __( 'Waypoints Settings', 'plan-your-day' ), + __( 'Waypoints', 'plan-your-day' ), 'manage_options', Settings::PAGE_SLUG, [ $this, 'render' ] @@ -102,6 +102,24 @@ public function register(): void { 'plan_your_day_default_location' ); + add_settings_section( + 'plan_your_day_appearance', + __( 'Appearance', 'plan-your-day' ), + [ $this, 'render_appearance_section' ], + Settings::PAGE_SLUG + ); + + $this->add_field( + 'color_mode_default', + __( 'Default color mode', 'plan-your-day' ), + __( 'Choose the public planner color mode before a visitor makes their own choice. System follows the visitor browser or OS preference.', 'plan-your-day' ), + 'select', + [ + 'choices' => Settings::color_mode_choices(), + ], + 'plan_your_day_appearance' + ); + add_settings_section( 'plan_your_day_planner_behavior', __( 'Planner Behavior', 'plan-your-day' ), @@ -173,13 +191,6 @@ public function register(): void { 'plan_your_day_planner_behavior' ); - add_settings_section( - 'plan_your_day_interface_copy', - __( 'Interface Copy', 'plan-your-day' ), - [ $this, 'render_interface_copy_section' ], - Settings::PAGE_SLUG - ); - add_settings_section( 'plan_your_day_categories', __( 'Categories', 'plan-your-day' ), @@ -187,21 +198,14 @@ public function register(): void { Settings::PAGE_SLUG ); - $this->add_field( - 'use_preset_categories', - __( 'Starter category fallback', 'plan-your-day' ), - __( 'When the saved category list is empty, show the built-in starter categories instead of no category buttons.', 'plan-your-day' ), - 'checkbox', - [], - 'plan_your_day_categories' - ); - $this->add_field( 'categories', - __( 'Categories', 'plan-your-day' ), - __( 'Manage the category buttons shown to visitors. The Google search query is the phrase sent to Google Places.', 'plan-your-day' ), + '', + '', 'categories', - [], + [ + 'field_class' => 'plan-your-day-categories-field', + ], 'plan_your_day_categories' ); @@ -287,6 +291,13 @@ public function register(): void { 'plan_your_day_google_cache' ); + add_settings_section( + 'plan_your_day_interface_copy', + __( 'Interface Copy', 'plan-your-day' ), + [ $this, 'render_interface_copy_section' ], + Settings::PAGE_SLUG + ); + add_settings_section( 'plan_your_day_rate_limiting', __( 'Rate Limiting', 'plan-your-day' ), @@ -327,7 +338,7 @@ public function register(): void { public function render(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Plan Your Day settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); } ?> @@ -408,7 +419,7 @@ public function enqueue_assets( string $hook_suffix ): void { wp_enqueue_script( 'plan-your-day-admin-settings', PLAN_YOUR_DAY_PLUGIN_URL . 'assets/js/admin-settings.js', - [], + [ 'jquery', 'jquery-ui-sortable' ], PLAN_YOUR_DAY_VERSION, [ 'in_footer' => true, @@ -527,7 +538,7 @@ private function build_setup_status_checks(): array { count( $active_categories ), count( $saved_categories ) ) - : __( 'No active categories are available. Add at least one category or re-enable the starter category fallback.', 'plan-your-day' ), + : __( 'No active categories are available. Add at least one category, or leave the planner to custom search only.', 'plan-your-day' ), 'type' => [] !== $active_categories ? 'success' : 'warning', ], ]; @@ -554,6 +565,13 @@ public function render_planner_behavior_section(): void { ); } + public function render_appearance_section(): void { + printf( + '

    %s

    ', + esc_html__( 'Set the public planner default color mode. Visitors can switch modes on the planner without changing the saved plugin setting.', 'plan-your-day' ) + ); + } + public function render_interface_copy_section(): void { printf( '

    %s

    ', @@ -566,7 +584,7 @@ public function render_interface_copy_section(): void { public function render_categories_section(): void { printf( '

    %s

    ', - esc_html__( 'Edit the category buttons shown on the public planner. You can add, disable, remove, and reorder rows here. Fresh installs start from the built-in starter list, and the fallback toggle only applies when the saved list is empty.', 'plan-your-day' ) + esc_html__( 'Edit the category buttons shown on the public planner. You can add, disable, delete, and reorder rows here, or leave the list empty to use custom search only.', 'plan-your-day' ) ); } @@ -604,7 +622,7 @@ public function render_missing_required_settings_notice(): void { esc_html( sprintf( /* translators: %s is a comma-separated list of missing settings. */ - __( 'Plan Your Day needs required settings before the public planner can render: %s.', 'plan-your-day' ), + __( 'Waypoints needs required settings before the public planner can render: %s.', 'plan-your-day' ), $missing ) ), @@ -615,7 +633,7 @@ public function render_missing_required_settings_notice(): void { public function handle_clear_google_cache(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Plan Your Day settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); } check_admin_referer( 'plan_your_day_clear_google_cache' ); @@ -635,7 +653,7 @@ public function handle_clear_google_cache(): void { public function handle_clear_google_cache_scope(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Plan Your Day settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); } check_admin_referer( 'plan_your_day_clear_google_cache_scope' ); @@ -657,7 +675,7 @@ public function handle_clear_google_cache_scope(): void { public function handle_clear_google_cache_place(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Plan Your Day settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); } check_admin_referer( 'plan_your_day_clear_google_cache_place' ); @@ -679,7 +697,7 @@ public function handle_clear_google_cache_place(): void { public function handle_test_google_api(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Plan Your Day settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); } check_admin_referer( 'plan_your_day_test_google_api' ); @@ -891,18 +909,27 @@ private function add_field( array $attributes = [], string $section = 'plan_your_day_google_api' ): void { + $field_class = (string) ( $attributes['field_class'] ?? '' ); + unset( $attributes['field_class'] ); + + $args = [ + 'attributes' => $attributes, + 'description' => $description, + 'key' => $key, + 'type' => $type, + ]; + + if ( '' !== $field_class ) { + $args['class'] = $field_class; + } + add_settings_field( 'plan_your_day_' . $key, $label, [ $this, 'render_field' ], Settings::PAGE_SLUG, $section, - [ - 'attributes' => $attributes, - 'description' => $description, - 'key' => $key, - 'type' => $type, - ] + $args ); } @@ -982,9 +1009,11 @@ private function render_interface_copy_group( string $group ): void { $type = (string) $definition['type']; $description = (string) $definition['description']; ?> -
    +
    + + - - + + - - + + - - + + $copy['loading_results_heading'], - 'body' => $copy['loading_results_body'], + 'heading' => __( 'Loading Google results', 'plan-your-day' ), + 'body' => $loading_results_body, ]; - $planner_state['overview'] = $copy['loading_results_body']; + $planner_state['overview'] = $loading_results_body; } if ( $has_selected_waypoints ) { - $planner_state['trip_count_label'] = $copy['loading_trip_count']; + $loading_trip_body = __( 'The planner is loading your saved trip through the secure request path.', 'plan-your-day' ); + $planner_state['trip_count_label'] = __( 'Loading trip waypoints...', 'plan-your-day' ); $planner_state['trip_empty_state'] = [ - 'heading' => $copy['loading_trip_heading'], - 'body' => $copy['loading_trip_body'], + 'heading' => __( 'Loading trip waypoints', 'plan-your-day' ), + 'body' => $loading_trip_body, ]; - $planner_state['overview'] = $copy['loading_trip_body']; + $planner_state['overview'] = $loading_trip_body; } if ( $has_selected_waypoints ) { - $planner_state['preview_mode_label'] = $copy['loading_trip_preview_mode']; + $planner_state['preview_mode_label'] = __( 'Loading trip preview', 'plan-your-day' ); $planner_state['preview_empty_state'] = [ - 'heading' => $copy['loading_trip_preview_heading'], - 'body' => $copy['loading_trip_preview_body'], + 'heading' => __( 'Loading trip preview', 'plan-your-day' ), + 'body' => __( 'The planner is loading your saved trip through the secure request path.', 'plan-your-day' ), ]; } elseif ( $has_search ) { - $planner_state['preview_mode_label'] = $copy['loading_search_preview_mode']; + $planner_state['preview_mode_label'] = __( 'Loading search preview', 'plan-your-day' ); $planner_state['preview_empty_state'] = [ - 'heading' => $copy['loading_search_preview_heading'], - 'body' => $copy['loading_search_preview_body'], + 'heading' => __( 'Loading search preview', 'plan-your-day' ), + 'body' => __( 'The planner is loading your saved search through the secure request path.', 'plan-your-day' ), ]; } diff --git a/plugin/plan-your-day/src/Frontend/InterfaceCopy.php b/plugin/plan-your-day/src/Frontend/InterfaceCopy.php index 8271ec3..75e003a 100644 --- a/plugin/plan-your-day/src/Frontend/InterfaceCopy.php +++ b/plugin/plan-your-day/src/Frontend/InterfaceCopy.php @@ -36,7 +36,7 @@ public static function groups(): array { ], 'search_results' => [ 'label' => __( 'Search And Results', 'plan-your-day' ), - 'description' => __( 'Edit category search labels, result actions, and search-related empty states.', 'plan-your-day' ), + 'description' => __( 'Edit the search section heading and category search placeholder.', 'plan-your-day' ), ], 'trip' => [ 'label' => __( 'Trip Builder', 'plan-your-day' ), @@ -188,7 +188,7 @@ private static function general_definitions(): array { 'group' => 'general', 'type' => 'text', 'required' => true, - 'default' => __( 'Plan Your Day setup needed', 'plan-your-day' ), + 'default' => __( 'Waypoints setup needed', 'plan-your-day' ), ], 'setup_notice_body' => [ 'label' => __( 'Setup notice message', 'plan-your-day' ), @@ -205,7 +205,7 @@ private static function general_definitions(): array { 'group' => 'general', 'type' => 'text', 'required' => true, - 'default' => __( 'Open Plan Your Day settings', 'plan-your-day' ), + 'default' => __( 'Open Waypoints settings', 'plan-your-day' ), ], 'hero_eyebrow' => [ 'label' => __( 'Top section eyebrow', 'plan-your-day' ), @@ -221,7 +221,7 @@ private static function general_definitions(): array { 'group' => 'general', 'type' => 'text', 'required' => true, - 'default' => __( 'Plan Your Day', 'plan-your-day' ), + 'default' => __( 'Waypoints', 'plan-your-day' ), ], 'hero_intro' => [ 'label' => __( 'Top section intro', 'plan-your-day' ), @@ -248,23 +248,6 @@ private static function starting_point_definitions(): array { 'required' => true, 'default' => __( 'Starting point', 'plan-your-day' ), ], - 'start_card_help' => [ - 'label' => __( 'Starting point helper text', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this helper sentence.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'textarea', - 'rows' => 2, - 'required' => false, - 'default' => __( 'Choose where the trip starts.', 'plan-your-day' ), - ], - 'start_mode_legend' => [ - 'label' => __( 'Starting point options label', 'plan-your-day' ), - 'description' => __( 'Used for screen readers.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Starting point mode', 'plan-your-day' ), - ], 'start_mode_current_label' => [ 'label' => __( 'Current location option label', 'plan-your-day' ), 'description' => '', @@ -273,23 +256,6 @@ private static function starting_point_definitions(): array { 'required' => true, 'default' => __( 'Current location handoff', 'plan-your-day' ), ], - 'start_mode_current_description' => [ - 'label' => __( 'Current location option helper text', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this helper line.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'textarea', - 'rows' => 2, - 'required' => false, - 'default' => __( 'Use the configured default location for on-page previews. Google Maps can start from the visitor\'s current location during handoff.', 'plan-your-day' ), - ], - 'start_mode_default_description' => [ - 'label' => __( 'Default location option helper text', 'plan-your-day' ), - 'description' => __( 'Use {default_location} where the configured default location label should appear. Leave blank to hide this helper line.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'text', - 'required' => false, - 'default' => __( 'Start from {default_location}.', 'plan-your-day' ), - ], 'start_mode_custom_label' => [ 'label' => __( 'Custom start option label', 'plan-your-day' ), 'description' => '', @@ -307,14 +273,6 @@ private static function starting_point_definitions(): array { 'required' => false, 'default' => __( 'Enter a hotel name, landmark, or street address.', 'plan-your-day' ), ], - 'custom_start_label' => [ - 'label' => __( 'Custom start field label', 'plan-your-day' ), - 'description' => '', - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Custom starting point', 'plan-your-day' ), - ], 'custom_start_placeholder' => [ 'label' => __( 'Custom start placeholder', 'plan-your-day' ), 'description' => __( 'Leave blank to remove the placeholder text.', 'plan-your-day' ), @@ -323,72 +281,6 @@ private static function starting_point_definitions(): array { 'required' => false, 'default' => __( 'Hotel name or street address', 'plan-your-day' ), ], - 'update_results_button' => [ - 'label' => __( 'Update results button', 'plan-your-day' ), - 'description' => '', - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Update results', 'plan-your-day' ), - ], - 'start_current_message' => [ - 'label' => __( 'Current location status message', 'plan-your-day' ), - 'description' => __( 'Shown in the planner status message list when current location handoff is active.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'The on-page results and preview use the configured default starting point. Google Maps will start from the visitor\'s current location during handoff.', 'plan-your-day' ), - ], - 'start_custom_missing_message' => [ - 'label' => __( 'Missing custom start warning', 'plan-your-day' ), - 'description' => __( 'Shown in the planner status message list when custom start mode is selected without an address.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'Add a custom address to replace the default fallback before finalizing the trip start.', 'plan-your-day' ), - ], - 'start_current_handoff_label' => [ - 'label' => __( 'Current location summary label', 'plan-your-day' ), - 'description' => __( 'Shown in the trip summary when current location handoff is active.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Current location', 'plan-your-day' ), - ], - 'start_current_handoff_summary' => [ - 'label' => __( 'Current location summary phrase', 'plan-your-day' ), - 'description' => __( 'Used inside route summary sentences.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'your current location', 'plan-your-day' ), - ], - 'start_default_fallback_label' => [ - 'label' => __( 'Default fallback summary label', 'plan-your-day' ), - 'description' => __( 'Shown in the trip summary when custom start mode is selected without an address.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Default location fallback', 'plan-your-day' ), - ], - 'start_default_fallback_summary' => [ - 'label' => __( 'Default fallback summary phrase', 'plan-your-day' ), - 'description' => __( 'Used inside route summary sentences when custom start mode is selected without an address.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'the default location until a custom starting point is provided', 'plan-your-day' ), - ], - 'start_default_location_fallback' => [ - 'label' => __( 'Default location fallback label', 'plan-your-day' ), - 'description' => __( 'Used only if the configured default location label is missing.', 'plan-your-day' ), - 'group' => 'starting_point', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Default location', 'plan-your-day' ), - ], ]; } @@ -405,23 +297,6 @@ private static function search_results_definitions(): array { 'required' => true, 'default' => __( 'What are you looking for?', 'plan-your-day' ), ], - 'category_card_help' => [ - 'label' => __( 'Search section helper text', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this helper sentence.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 2, - 'required' => false, - 'default' => __( 'Search for any category or use a category shortcut to load Google results.', 'plan-your-day' ), - ], - 'category_search_label' => [ - 'label' => __( 'Category search field label', 'plan-your-day' ), - 'description' => __( 'Used for screen readers.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Search categories', 'plan-your-day' ), - ], 'category_search_placeholder' => [ 'label' => __( 'Category search placeholder', 'plan-your-day' ), 'description' => __( 'Leave blank to remove the placeholder text.', 'plan-your-day' ), @@ -430,222 +305,6 @@ private static function search_results_definitions(): array { 'required' => false, 'default' => __( 'Search categories', 'plan-your-day' ), ], - 'category_search_button' => [ - 'label' => __( 'Category search button', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Search', 'plan-your-day' ), - ], - 'custom_results_heading' => [ - 'label' => __( 'Custom search results heading', 'plan-your-day' ), - 'description' => __( 'Use {search} where the current search term should appear.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Results for {search}', 'plan-your-day' ), - ], - 'custom_results_description' => [ - 'label' => __( 'Custom search results helper text', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this helper line.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => false, - 'default' => __( 'Custom category search results.', 'plan-your-day' ), - ], - 'more_results_button' => [ - 'label' => __( 'More results button', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'More results', 'plan-your-day' ), - ], - 'view_in_google_maps' => [ - 'label' => __( 'Result map link label', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'View in Google Maps', 'plan-your-day' ), - ], - 'view_place_in_google_maps_aria' => [ - 'label' => __( 'Result map link screen reader label', 'plan-your-day' ), - 'description' => __( 'Use {place} where the place name should appear.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'View {place} in Google Maps', 'plan-your-day' ), - ], - 'in_trip' => [ - 'label' => __( 'Already added label', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'In trip', 'plan-your-day' ), - ], - 'already_in_trip_aria' => [ - 'label' => __( 'Already added screen reader label', 'plan-your-day' ), - 'description' => __( 'Use {place} where the place name should appear.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( '{place} is already in the trip', 'plan-your-day' ), - ], - 'add_to_trip' => [ - 'label' => __( 'Add to trip button', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Add to trip', 'plan-your-day' ), - ], - 'add_waypoint_aria' => [ - 'label' => __( 'Add to trip screen reader label', 'plan-your-day' ), - 'description' => __( 'Use {place} where the place name should appear.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Add {place} to trip', 'plan-your-day' ), - ], - 'search_results_unavailable_heading' => [ - 'label' => __( 'Results unavailable heading', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Google results unavailable', 'plan-your-day' ), - ], - 'search_results_unavailable_body' => [ - 'label' => __( 'Results unavailable message', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'Google place results are unavailable right now. Try again later or open the Google Maps handoff link.', 'plan-your-day' ), - ], - 'search_results_prompt_heading' => [ - 'label' => __( 'No search yet heading', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Search for any category', 'plan-your-day' ), - ], - 'search_results_prompt_body_with_categories' => [ - 'label' => __( 'No search yet message with category shortcuts', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this helper text when category shortcuts are available.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 3, - 'required' => false, - 'default' => __( 'Use the search box or choose a category to load real place results.', 'plan-your-day' ), - ], - 'search_results_prompt_body_no_categories' => [ - 'label' => __( 'No search yet message without category shortcuts', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this helper text when no category shortcuts are available.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 3, - 'required' => false, - 'default' => __( 'Use the search box to load real place results.', 'plan-your-day' ), - ], - 'no_matching_results_heading' => [ - 'label' => __( 'No matching results heading', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'No matching Google results', 'plan-your-day' ), - ], - 'no_matching_results_body' => [ - 'label' => __( 'No matching results message', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this helper text.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 2, - 'required' => false, - 'default' => __( 'Try a different search or change the starting area.', 'plan-your-day' ), - ], - 'maps_link_label_search' => [ - 'label' => __( 'Search-mode Google Maps link label', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Explore in Google Maps', 'plan-your-day' ), - ], - 'preview_mode_label_search' => [ - 'label' => __( 'Search-mode preview label', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Google place search', 'plan-your-day' ), - ], - 'overview_initial_with_categories' => [ - 'label' => __( 'Initial search overview with category shortcuts', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this overview sentence in the trip summary.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 3, - 'required' => false, - 'default' => __( 'Search for any category or pick one below to load Google results, then add exact places to your trip.', 'plan-your-day' ), - ], - 'search_results_count_single' => [ - 'label' => __( 'Single result count label', 'plan-your-day' ), - 'description' => __( 'Use {count} where the number of results should appear.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( '{count} Google result', 'plan-your-day' ), - ], - 'search_results_count_plural' => [ - 'label' => __( 'Plural result count label', 'plan-your-day' ), - 'description' => __( 'Use {count} where the number of results should appear.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( '{count} Google results', 'plan-your-day' ), - ], - 'no_results_loaded_label' => [ - 'label' => __( 'No results loaded label', 'plan-your-day' ), - 'description' => '', - 'group' => 'search_results', - 'type' => 'text', - 'required' => true, - 'default' => __( 'No Google results loaded', 'plan-your-day' ), - ], - 'overview_browse_search' => [ - 'label' => __( 'Search overview after results load', 'plan-your-day' ), - 'description' => __( 'Use {search} for the active search and {start} for the current start summary. Leave blank to hide this overview sentence.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 3, - 'required' => false, - 'default' => __( 'Browsing Google results for {search} near {start}. Add any result to start building a walking trip.', 'plan-your-day' ), - ], - 'search_preview_key_warning' => [ - 'label' => __( 'Search preview key warning', 'plan-your-day' ), - 'description' => __( 'Shown when map preview is enabled without a valid Google Maps Embed API key.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'Add a valid Google Maps Embed API key before relying on the on-site search preview.', 'plan-your-day' ), - ], - 'overview_initial_no_categories' => [ - 'label' => __( 'Initial search overview without category shortcuts', 'plan-your-day' ), - 'description' => __( 'Leave blank to hide this overview sentence when no category shortcuts are available.', 'plan-your-day' ), - 'group' => 'search_results', - 'type' => 'textarea', - 'rows' => 3, - 'required' => false, - 'default' => __( 'Search for any category to load Google results, then add exact places to your trip.', 'plan-your-day' ), - ], ]; } @@ -688,30 +347,6 @@ private static function trip_definitions(): array { 'required' => false, 'default' => __( 'Add exact places from Google, then use the move controls to set the walking trip order.', 'plan-your-day' ), ], - 'clear_trip' => [ - 'label' => __( 'Clear trip button', 'plan-your-day' ), - 'description' => '', - 'group' => 'trip', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Clear trip', 'plan-your-day' ), - ], - 'move_up' => [ - 'label' => __( 'Move up button', 'plan-your-day' ), - 'description' => '', - 'group' => 'trip', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Move up', 'plan-your-day' ), - ], - 'move_down' => [ - 'label' => __( 'Move down button', 'plan-your-day' ), - 'description' => '', - 'group' => 'trip', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Move down', 'plan-your-day' ), - ], 'move_waypoint_up_aria' => [ 'label' => __( 'Move up screen reader label', 'plan-your-day' ), 'description' => __( 'Use {place} where the place name should appear.', 'plan-your-day' ), @@ -1054,106 +689,6 @@ private static function status_definitions(): array { 'required' => true, 'default' => __( 'Loading planner state through a verified request.', 'plan-your-day' ), ], - 'loading_results_label' => [ - 'label' => __( 'Loading results count label', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading Google results...', 'plan-your-day' ), - ], - 'loading_results_heading' => [ - 'label' => __( 'Loading results heading', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading Google results', 'plan-your-day' ), - ], - 'loading_results_body' => [ - 'label' => __( 'Loading results message', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'The planner is loading your saved search through the secure request path.', 'plan-your-day' ), - ], - 'loading_trip_count' => [ - 'label' => __( 'Loading trip count label', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading trip waypoints...', 'plan-your-day' ), - ], - 'loading_trip_heading' => [ - 'label' => __( 'Loading trip heading', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading trip waypoints', 'plan-your-day' ), - ], - 'loading_trip_body' => [ - 'label' => __( 'Loading trip message', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'The planner is loading your saved trip through the secure request path.', 'plan-your-day' ), - ], - 'loading_trip_preview_mode' => [ - 'label' => __( 'Loading trip preview label', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading trip preview', 'plan-your-day' ), - ], - 'loading_trip_preview_heading' => [ - 'label' => __( 'Loading trip preview heading', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading trip preview', 'plan-your-day' ), - ], - 'loading_trip_preview_body' => [ - 'label' => __( 'Loading trip preview message', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'The planner is loading your saved trip through the secure request path.', 'plan-your-day' ), - ], - 'loading_search_preview_mode' => [ - 'label' => __( 'Loading search preview label', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading search preview', 'plan-your-day' ), - ], - 'loading_search_preview_heading' => [ - 'label' => __( 'Loading search preview heading', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'text', - 'required' => true, - 'default' => __( 'Loading search preview', 'plan-your-day' ), - ], - 'loading_search_preview_body' => [ - 'label' => __( 'Loading search preview message', 'plan-your-day' ), - 'description' => '', - 'group' => 'status_messages', - 'type' => 'textarea', - 'rows' => 3, - 'required' => true, - 'default' => __( 'The planner is loading your saved search through the secure request path.', 'plan-your-day' ), - ], 'request_failed' => [ 'label' => __( 'Generic request failure message', 'plan-your-day' ), 'description' => __( 'Shown when a frontend request fails without a more specific message.', 'plan-your-day' ), diff --git a/plugin/plan-your-day/src/Frontend/PlannerRenderer.php b/plugin/plan-your-day/src/Frontend/PlannerRenderer.php index a1d9126..29d8179 100644 --- a/plugin/plan-your-day/src/Frontend/PlannerRenderer.php +++ b/plugin/plan-your-day/src/Frontend/PlannerRenderer.php @@ -8,7 +8,6 @@ use Acodebeard\PlanYourDay\Planner\PlannerStateBuilder; use Acodebeard\PlanYourDay\Planner\RequestStateParser; use Acodebeard\PlanYourDay\Rest\PlannerRoutes; -use Acodebeard\PlanYourDay\Security\VisitorTokenManager; use Acodebeard\PlanYourDay\Settings\Settings; defined( 'ABSPATH' ) || exit; @@ -19,22 +18,19 @@ final class PlannerRenderer { private RequestStateParser $request_state_parser; private PlannerStateBuilder $planner_state_builder; private PlannerPayloadBuilder $planner_payload_builder; - private VisitorTokenManager $visitor_token_manager; public function __construct( Settings $settings, CategoryCatalog $category_catalog, RequestStateParser $request_state_parser, PlannerStateBuilder $planner_state_builder, - PlannerPayloadBuilder $planner_payload_builder, - VisitorTokenManager $visitor_token_manager + PlannerPayloadBuilder $planner_payload_builder ) { $this->settings = $settings; $this->category_catalog = $category_catalog; $this->request_state_parser = $request_state_parser; $this->planner_state_builder = $planner_state_builder; $this->planner_payload_builder = $planner_payload_builder; - $this->visitor_token_manager = $visitor_token_manager; } public function render( array $request = [], string $action_url = '' ): string { @@ -67,7 +63,8 @@ public function render( array $request = [], string $action_url = '' ): string { $action_url = '' !== $action_url ? $action_url : $this->get_current_url(); $form_action = $action_url . '#' . $instance_id; $maps_link_enabled = '' !== $planner_state['maps_url']; - $endpoint_token = $this->visitor_token_manager->get_endpoint_token(); + $color_mode_default = $this->settings->get_color_mode_default(); + $initial_color_mode = Settings::COLOR_MODE_SYSTEM === $color_mode_default ? '' : $color_mode_default; ob_start(); ?> @@ -75,6 +72,10 @@ public function render( array $request = [], string $action_url = '' ): string { class="plan-your-day" id="" aria-labelledby="" + data-plan-color-mode-default="" + + data-plan-color-mode="" + data-plan-root>
    @@ -87,6 +88,16 @@ class="plan-your-day"

    settings->get_frontend_copy_value( 'hero_intro' ) ); ?>

    +
    + +
    - + settings->get_frontend_copy_value( 'start_card_help' ); + $custom_status = (string) ( $planner_state['custom_start_status'] ?? '' ); + $custom_status = in_array( $custom_status, [ 'found', 'not_found' ], true ) ? $custom_status : ''; + $custom_status_text = ''; + + if ( 'found' === $custom_status ) { + $custom_status_text = __( 'Starting address found. Results are ready.', 'plan-your-day' ); + } elseif ( 'not_found' === $custom_status ) { + $custom_status_text = __( 'Starting address was not found.', 'plan-your-day' ); + } ?>

    settings->get_frontend_copy_value( 'start_card_heading' ) ); ?>

    - -

    -
    -
    > - settings->get_frontend_copy_value( 'start_mode_legend' ) ); ?> +
    +
    $start_point ) : ?> @@ -195,16 +213,22 @@ class="plan-your-day__start-toggle"
    > - - + +
    + + +
    +

    @@ -216,7 +240,7 @@ class="plan-your-day__custom-start"
    - +
    @@ -231,7 +255,7 @@ private function render_category_card( string $instance_id, array $planner_state $custom_results_trigger_id = $instance_id . '-custom-results-trigger'; $custom_results_panel_id = $instance_id . '-custom-results-panel'; $has_custom_search = (bool) $planner_state['is_custom_search']; - $category_help_text = $this->settings->get_frontend_copy_value( 'category_card_help' ); + $category_help_text = __( 'Search for any category or use a category shortcut to load Google results.', 'plan-your-day' ); ?>
    @@ -244,7 +268,7 @@ private function render_category_card( string $instance_id, array $planner_state
    @@ -279,22 +303,21 @@ class="plan-your-day__category-trigger" settings->format_frontend_copy( - 'custom_results_heading', - [ - 'search' => (string) $planner_state['active_search_label'], - ] + ? sprintf( + /* translators: %s: active Google place search label. */ + __( 'Results for %s', 'plan-your-day' ), + (string) $planner_state['active_search_label'] ) : $results_empty_state['heading'] ); ?> - settings->get_frontend_copy_value( 'custom_results_description' ) ); ?> + @@ -338,7 +361,7 @@ class="plan-your-day__category-trigger" @@ -385,7 +408,7 @@ private function render_search_results_panel( array $planner_state, array $resul
    @@ -393,9 +416,24 @@ private function render_search_results_panel( array $planner_state, array $resul } private function render_search_result( array $result, array $selected_waypoint_ids ): void { - $place_id = (string) ( $result['id'] ?? '' ); - $label = (string) ( $result['label'] ?? '' ); - $is_in_trip = in_array( $place_id, $selected_waypoint_ids, true ); + $place_id = (string) ( $result['id'] ?? '' ); + $label = (string) ( $result['label'] ?? '' ); + $is_in_trip = in_array( $place_id, $selected_waypoint_ids, true ); + $map_link_aria = sprintf( + /* translators: %s: place name. */ + __( 'View %s in Google Maps', 'plan-your-day' ), + $label + ); + $in_trip_aria = sprintf( + /* translators: %s: place name. */ + __( '%s is already in the trip', 'plan-your-day' ), + $label + ); + $add_trip_aria = sprintf( + /* translators: %s: place name. */ + __( 'Add %s to trip', 'plan-your-day' ), + $label + ); ?>
  • @@ -408,42 +446,30 @@ private function render_search_result( array $result, array $selected_waypoint_i
    - settings->get_frontend_copy_value( 'view_in_google_maps' ) ); ?> + class="plan-your-day__result-link" + href="" + target="_blank" + rel="noopener noreferrer" + aria-label=""> + - - settings->get_frontend_copy_value( 'in_trip' ) ); ?> + +
    @@ -475,7 +501,7 @@ private function render_trip_card( string $instance_id, array $planner_state ):
    @@ -524,7 +550,7 @@ class="plan-your-day__reorder-button plan-your-day__reorder-button--up" ?> " > - settings->get_frontend_copy_value( 'move_up' ) ); ?> + ', $output ); + self::assertStringContainsString( '', $output ); + self::assertStringContainsString( 'aria-describedby="plan-your-day-1-custom-start-status"', $output ); + self::assertStringContainsString( 'data-plan-custom-start-indicator', $output ); + self::assertStringContainsString( 'id="plan-your-day-1-custom-start-status"', $output ); + self::assertStringContainsString( 'data-plan-custom-start-status', $output ); + self::assertStringContainsString( '

    Search for any category or use a category shortcut to load Google results.

    ', $output ); + self::assertStringContainsString( '', $output ); + self::assertStringContainsString( '