diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..033eb92
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,55 @@
+name: WordPress theme CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262c # v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: "22"
+ - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
+ with:
+ php-version: "8.1"
+ coverage: none
+ - name: Reject symbolic links
+ run: test -z "$(find . -type l -not -path './.git/*' -print -quit)"
+ - name: Install theme build dependencies
+ run: npm ci
+ - name: Build native theme assets
+ run: npm run build
+ - name: Verify committed assets are reproducible
+ run: git diff --exit-code
+ - name: Parse all theme PHP
+ run: npm run lint:php
+ - name: Prepare the WordPress.org package
+ run: node scripts/prepare-wordpress-org-package.mjs --out "$RUNNER_TEMP/wordpress-org/funkycommerce-headless"
+ - name: Validate packaged PHP syntax
+ run: find "$RUNNER_TEMP/wordpress-org/funkycommerce-headless" -type f -name '*.php' -print0 | xargs -0 -n1 php -l
+
+ wordpress-org-preflight:
+ if: vars.WPORG_PREFLIGHT_ENABLED == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262c # v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: "22"
+ - name: Prepare the WordPress.org package
+ run: node scripts/prepare-wordpress-org-package.mjs --out "$RUNNER_TEMP/wordpress-org/funkycommerce-headless"
+ - name: Run the WordPress Theme Review checks
+ uses: WordPress/theme-review-action@84400b232998116c9d1651502575c60262cceef9
+ with:
+ root-folder: ${{ runner.temp }}/wordpress-org/funkycommerce-headless
+ accessible-ready: false
+ ui-debug: false
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..9289790
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,160 @@
+name: Publish WordPress theme
+
+on:
+ workflow_run:
+ workflows: ["WordPress theme CI"]
+ branches: [main]
+ types: [completed]
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+concurrency:
+ group: publish-wordpress-theme
+ cancel-in-progress: false
+
+env:
+ PACKAGE_SLUG: funkycommerce-headless
+ VERSION_FILE: style.css
+ WPORG_SLUG: funkycommerce-headless
+
+jobs:
+ release:
+ if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_repository.full_name == github.repository)
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ outputs:
+ archive: ${{ steps.package.outputs.archive }}
+ publish: ${{ steps.package.outputs.publish }}
+ source-sha: ${{ steps.source.outputs.sha }}
+ version: ${{ steps.package.outputs.version }}
+ wordpress-org-archive: ${{ steps.package.outputs.wordpress-org-archive }}
+ steps:
+ - id: source
+ name: Resolve the validated source commit
+ env:
+ VALIDATED_SHA: ${{ github.event.workflow_run.head_sha }}
+ run: |
+ sha="${VALIDATED_SHA:-$GITHUB_SHA}"
+ echo "sha=$sha" >> "$GITHUB_OUTPUT"
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262c # v4
+ with:
+ ref: ${{ steps.source.outputs.sha }}
+ fetch-depth: 0
+ persist-credentials: false
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: "22"
+ - id: package
+ name: Build the release archive
+ run: |
+ version="$(grep -m1 -E '^Version:' "$VERSION_FILE" | sed -E 's/^[^:]+:[[:space:]]*//')"
+ stable="$(grep -m1 -E '^Stable tag:' readme.txt | sed -E 's/^[^:]+:[[:space:]]*//')"
+ if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
+ echo "::error::Invalid package version: $version"
+ exit 1
+ fi
+ if [ "$version" != "$stable" ]; then
+ echo "::error::Version $version does not match readme Stable tag $stable."
+ exit 1
+ fi
+ archive="$PACKAGE_SLUG-$version.zip"
+ wordpress_org_archive="$PACKAGE_SLUG-$version-wordpress-org.zip"
+ if git ls-remote --exit-code --tags origin "refs/tags/v$version" >/dev/null; then
+ echo "publish=false" >> "$GITHUB_OUTPUT"
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+
+ public_stage="$RUNNER_TEMP/public-package/$PACKAGE_SLUG"
+ mkdir -p "$public_stage"
+ rsync -a ./ "$public_stage/" \
+ --exclude '.git*' \
+ --exclude '.github' \
+ --exclude '.monorepo-source.json' \
+ --exclude '.wordpress-org' \
+ --exclude 'CODE_OF_CONDUCT.md' \
+ --exclude 'CONTRIBUTING.md' \
+ --exclude 'EXTRACTION_STATUS.md' \
+ --exclude 'README.md' \
+ --exclude 'SECURITY.md' \
+ --exclude 'docs' \
+ --exclude 'scripts'
+ stage="$RUNNER_TEMP/package/$PACKAGE_SLUG"
+ node scripts/prepare-wordpress-org-package.mjs --out "$stage"
+ if find "$stage" -type l -print -quit | grep -q .; then
+ echo "::error::Release packages must not contain symbolic links."
+ exit 1
+ fi
+ (cd "$(dirname "$public_stage")" && zip -X -q -r "$GITHUB_WORKSPACE/$archive" "$PACKAGE_SLUG")
+ (cd "$(dirname "$stage")" && zip -X -q -r "$GITHUB_WORKSPACE/$wordpress_org_archive" "$PACKAGE_SLUG")
+ echo "archive=$archive" >> "$GITHUB_OUTPUT"
+ echo "publish=true" >> "$GITHUB_OUTPUT"
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ echo "wordpress-org-archive=$wordpress_org_archive" >> "$GITHUB_OUTPUT"
+ - name: Publish the GitHub release
+ if: steps.package.outputs.publish == 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ SOURCE_SHA: ${{ steps.source.outputs.sha }}
+ VERSION: ${{ steps.package.outputs.version }}
+ run: |
+ gh release create "v$VERSION" \
+ "${{ steps.package.outputs.archive }}" \
+ "${{ steps.package.outputs.wordpress-org-archive }}" \
+ --target "$SOURCE_SHA" \
+ --title "$PACKAGE_SLUG $VERSION" \
+ --generate-notes
+ - name: Preserve the WordPress.org submission package
+ if: steps.package.outputs.publish == 'true'
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: funkycommerce-headless-${{ steps.package.outputs.version }}
+ path: ${{ steps.package.outputs.wordpress-org-archive }}
+ if-no-files-found: error
+
+ wordpress-org:
+ if: needs.release.outputs.publish == 'true' && vars.WPORG_DEPLOY_ENABLED == 'true' && false
+ needs: release
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ environment: wordpress-org
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262c # v4
+ with:
+ ref: ${{ needs.release.outputs.source-sha }}
+ persist-credentials: false
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: "22"
+ - name: Require WordPress.org credentials
+ env:
+ WPORG_SVN_PASSWORD: ${{ secrets.WPORG_SVN_PASSWORD }}
+ WPORG_USERNAME: ${{ secrets.WPORG_USERNAME }}
+ run: |
+ test -n "$WPORG_USERNAME" || { echo "::error::Missing WPORG_USERNAME."; exit 1; }
+ test -n "$WPORG_SVN_PASSWORD" || { echo "::error::Missing WPORG_SVN_PASSWORD."; exit 1; }
+ - name: Deploy the approved theme release to WordPress.org
+ env:
+ VERSION: ${{ needs.release.outputs.version }}
+ WPORG_SVN_PASSWORD: ${{ secrets.WPORG_SVN_PASSWORD }}
+ WPORG_USERNAME: ${{ secrets.WPORG_USERNAME }}
+ run: |
+ svn_root="$RUNNER_TEMP/wordpress-org"
+ stage="$RUNNER_TEMP/package/$PACKAGE_SLUG"
+ node scripts/prepare-wordpress-org-package.mjs --out "$stage"
+ svn checkout "https://themes.svn.wordpress.org/$WPORG_SLUG" "$svn_root"
+ if svn info "https://themes.svn.wordpress.org/$WPORG_SLUG/$VERSION" >/dev/null 2>&1; then
+ echo "::error::WordPress.org version $VERSION already exists and cannot be overwritten."
+ exit 1
+ fi
+ mkdir "$svn_root/$VERSION"
+ rsync -a "$stage/" "$svn_root/$VERSION/"
+ svn add "$svn_root/$VERSION"
+ svn commit "$svn_root" \
+ --message "Release $VERSION from GitHub commit ${{ needs.release.outputs.source-sha }}" \
+ --username "$WPORG_USERNAME" \
+ --password "$WPORG_SVN_PASSWORD" \
+ --no-auth-cache \
+ --non-interactive
diff --git a/.monorepo-source.json b/.monorepo-source.json
new file mode 100644
index 0000000..7e37c49
--- /dev/null
+++ b/.monorepo-source.json
@@ -0,0 +1,14 @@
+{
+ "productId": "funkycommerce-headless",
+ "repository": "coded-letter/superfunky-theme",
+ "source": "coded-letter/coded-letter-monorepo@0aebd69d9cf0616630e4f090c3e4579abe17c63d",
+ "sourcePath": "workspace/backend/wordpress/themes/free/funkycommerce-headless",
+ "installSlug": "funkycommerce-headless",
+ "kind": "theme",
+ "versionFile": "style.css",
+ "wordpressOrg": {
+ "deploySupported": false,
+ "slug": "funkycommerce-headless",
+ "status": "blocked-theme-plugin-territory"
+ }
+}
diff --git a/EXTRACTION_STATUS.md b/EXTRACTION_STATUS.md
index 5cf9f55..e98790a 100644
--- a/EXTRACTION_STATUS.md
+++ b/EXTRACTION_STATUS.md
@@ -7,6 +7,7 @@ free-tier-only code.
| Date | Module | Status |
|---|---|---|
| 2025-08-03 | Full theme (v0.7.0) | ✅ Complete — all PHP files, templates, and schema published |
+| 2026-08-27 | Full theme (v1.2.6) | Complete — synchronized from `coded-letter/coded-letter-monorepo@0aebd69d9cf0616630e4f090c3e4579abe17c63d` |
## Free/Pro Tier Summary
@@ -32,6 +33,6 @@ When Pro is not active, `funkycommerce_is_pro()` returns `false` and:
## Process
1. Changes are developed in the private monorepo.
-2. Free-tier code is manually extracted and reviewed.
-3. Premium plugin references and secrets are verified absent.
-4. Code is pushed to this repo with an updated status entry.
+2. The catalog-allowlisted exporter replaces the public package files.
+3. Premium implementation and secrets are verified absent before and after export.
+4. A provenance-stamped pull request is reviewed and independently validated here.
diff --git a/assets/css/theme-source.css b/assets/css/theme-source.css
new file mode 100644
index 0000000..1c845bb
--- /dev/null
+++ b/assets/css/theme-source.css
@@ -0,0 +1,416 @@
+/*
+ * Superfunky native shell.
+ *
+ * Keep this layer intentionally small: migrated content should inherit WordPress
+ * block styles, while the shell supplies only structure, rhythm, and polish.
+ */
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --sf-shell-line: color-mix(in srgb, currentColor 13%, transparent);
+ --sf-shell-surface: color-mix(in srgb, var(--wp--preset--color--surface) 88%, transparent);
+ --sf-shell-radius: var(--wp--custom--shell--radius, 18px);
+ --sf-shell-shadow: 0 24px 70px -48px rgb(32 24 48 / 45%);
+ }
+
+ html {
+ scroll-behavior: smooth;
+ }
+
+ body {
+ min-height: 100vh;
+ text-rendering: optimizeLegibility;
+ }
+
+ :where(a, button, input, textarea, select):focus-visible {
+ outline: 2px solid var(--wp--preset--color--accent);
+ outline-offset: 3px;
+ }
+
+ ::selection {
+ background: var(--wp--preset--color--accent-soft);
+ color: var(--wp--preset--color--foreground);
+ }
+}
+
+@layer components {
+ .sf-skip-link {
+ background: var(--wp--preset--color--foreground);
+ border-radius: 999px;
+ color: var(--wp--preset--color--background);
+ font-size: 0.8rem;
+ font-weight: 650;
+ left: 1rem;
+ padding: 0.65rem 1rem;
+ position: fixed;
+ top: 1rem;
+ transform: translateY(-6rem);
+ transition: transform 160ms ease;
+ z-index: 1000;
+ }
+
+ .sf-skip-link:focus {
+ transform: translateY(0);
+ }
+
+ .sf-shell-header {
+ backdrop-filter: blur(18px) saturate(140%);
+ background: var(--sf-shell-surface);
+ border-bottom: 1px solid var(--sf-shell-line);
+ position: sticky;
+ top: var(--wp-admin--admin-bar--height, 0);
+ z-index: 50;
+ }
+
+ .sf-shell-header__inner {
+ min-height: 5.25rem;
+ padding-block: 1rem;
+ }
+
+ .sf-shell-identity {
+ min-width: 0;
+ }
+
+ .sf-shell-identity .wp-block-site-logo {
+ flex: 0 0 auto;
+ }
+
+ .sf-shell-identity .custom-logo {
+ border-radius: 11px;
+ }
+
+ .sf-shell-site-title {
+ font-family: var(--wp--preset--font-family--display);
+ font-size: clamp(1.05rem, 2vw, 1.25rem);
+ font-weight: 680;
+ letter-spacing: -0.035em;
+ line-height: 1.1;
+ }
+
+ .sf-shell-site-title a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+ .sf-shell-tagline {
+ color: var(--wp--preset--color--muted);
+ font-size: 0.72rem;
+ line-height: 1.3;
+ margin-top: 0.25rem;
+ }
+
+ .sf-shell-navigation {
+ font-size: 0.84rem;
+ font-weight: 580;
+ }
+
+ .sf-shell-navigation .wp-block-navigation-item__content {
+ border-radius: 999px;
+ color: inherit;
+ padding: 0.55rem 0.75rem;
+ text-decoration: none;
+ transition: background-color 160ms ease, color 160ms ease;
+ }
+
+ .sf-shell-navigation .wp-block-navigation-item__content:hover,
+ .sf-shell-navigation .current-menu-item > .wp-block-navigation-item__content {
+ background: var(--wp--preset--color--accent-soft);
+ color: var(--wp--preset--color--accent);
+ }
+
+ .sf-shell-navigation .wp-block-navigation__responsive-container.is-menu-open {
+ background: var(--wp--preset--color--background);
+ color: var(--wp--preset--color--foreground);
+ padding: 2rem;
+ }
+
+ .sf-shell-main {
+ min-height: 55vh;
+ }
+
+ .sf-shell-hero {
+ border-bottom: 1px solid var(--sf-shell-line);
+ overflow: hidden;
+ position: relative;
+ }
+
+ .sf-shell-hero::before {
+ background:
+ radial-gradient(circle at 15% 25%, color-mix(in srgb, var(--wp--preset--color--accent) 18%, transparent), transparent 38%),
+ radial-gradient(circle at 85% 10%, color-mix(in srgb, var(--wp--preset--color--highlight) 25%, transparent), transparent 34%);
+ content: "";
+ inset: 0;
+ pointer-events: none;
+ position: absolute;
+ }
+
+ .sf-shell-hero__inner {
+ padding-block: clamp(5rem, 13vw, 10rem);
+ position: relative;
+ }
+
+ .sf-shell-eyebrow {
+ color: var(--wp--preset--color--accent);
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ }
+
+ .sf-shell-hero .wp-block-site-title {
+ font-size: clamp(3rem, 10vw, 7.5rem);
+ font-weight: 680;
+ letter-spacing: -0.07em;
+ line-height: 0.9;
+ max-width: 11ch;
+ }
+
+ .sf-shell-hero .wp-block-site-tagline {
+ color: var(--wp--preset--color--muted);
+ font-size: clamp(1.05rem, 2vw, 1.35rem);
+ line-height: 1.5;
+ max-width: 42rem;
+ }
+
+ .sf-shell-content {
+ padding-block: clamp(3.5rem, 8vw, 7rem);
+ }
+
+ .sf-shell-card {
+ background: var(--wp--preset--color--surface);
+ border: 1px solid var(--sf-shell-line);
+ border-radius: var(--sf-shell-radius);
+ box-shadow: var(--sf-shell-shadow);
+ height: 100%;
+ overflow: hidden;
+ transition: border-color 180ms ease, transform 180ms ease;
+ }
+
+ .sf-shell-card:hover {
+ border-color: color-mix(in srgb, var(--wp--preset--color--accent) 34%, transparent);
+ transform: translateY(-3px);
+ }
+
+ .sf-shell-card .wp-block-post-featured-image {
+ margin: 0;
+ }
+
+ .sf-shell-card__body {
+ padding: clamp(1.25rem, 3vw, 2rem);
+ }
+
+ .sf-shell-card .wp-block-post-title a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+ .sf-shell-meta {
+ color: var(--wp--preset--color--muted);
+ font-size: 0.75rem;
+ letter-spacing: 0.02em;
+ }
+
+ .sf-shell-empty {
+ background: var(--wp--preset--color--surface);
+ border: 1px dashed var(--sf-shell-line);
+ border-radius: var(--sf-shell-radius);
+ padding: clamp(2rem, 6vw, 4rem);
+ text-align: center;
+ }
+
+ .sf-shell-footer {
+ background: var(--wp--preset--color--foreground);
+ color: var(--wp--preset--color--background);
+ }
+
+ .sf-shell-footer a {
+ color: inherit;
+ }
+
+ .sf-shell-footer__inner {
+ gap: 2rem;
+ padding-block: clamp(3rem, 7vw, 5rem);
+ }
+
+ .sf-shell-footer__brand {
+ max-width: 28rem;
+ }
+
+ .sf-shell-footer__brand .wp-block-site-tagline,
+ .sf-shell-footer__meta {
+ color: color-mix(in srgb, var(--wp--preset--color--background) 62%, transparent);
+ }
+
+ .sf-shell-footer__navigation {
+ font-size: 0.82rem;
+ }
+
+ .sf-shell-footer__navigation .wp-block-navigation-item__content {
+ text-decoration: none;
+ }
+
+ .sf-shell-footer__meta {
+ border-top: 1px solid color-mix(in srgb, var(--wp--preset--color--background) 15%, transparent);
+ padding-block: 1.25rem 2rem;
+ }
+}
+
+@media (max-width: 600px) {
+ .sf-shell-tagline {
+ display: none;
+ }
+
+ .sf-shell-header__inner {
+ min-height: 4.5rem;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ transition-duration: 0.01ms !important;
+ }
+}
+.funkycommerce-native-video-hero {
+ position: relative;
+ display: flex;
+ overflow: hidden;
+ align-items: flex-end;
+ min-height: 24rem;
+ padding: clamp(2rem, 6vw, 5rem);
+ border-radius: 1.5rem;
+ background: #09090b;
+ color: #fff;
+}
+
+.funkycommerce-native-video-hero-media,
+.funkycommerce-native-video-hero-poster,
+.funkycommerce-native-video-hero-overlay {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ border: 0;
+ object-fit: cover;
+}
+
+.funkycommerce-native-video-hero-overlay {
+ background: #000;
+}
+
+.funkycommerce-native-video-hero-content {
+ position: relative;
+ z-index: 1;
+ max-width: 48rem;
+}
+
+.funkycommerce-native-video-hero-controls {
+ position: absolute;
+ right: 1.25rem;
+ bottom: 1.25rem;
+ z-index: 2;
+ display: flex;
+ gap: 0.5rem;
+}
+
+.funkycommerce-native-video-hero-control {
+ display: inline-grid;
+ width: 2.75rem;
+ height: 2.75rem;
+ place-items: center;
+ border: 0;
+ border-radius: 9999px;
+ background: rgb(0 0 0 / 60%);
+ color: #fff;
+ cursor: pointer;
+}
+
+.funkycommerce-native-video-hero--center {
+ justify-content: center;
+ text-align: center;
+}
+
+.funkycommerce-native-video-hero--right {
+ justify-content: flex-end;
+ text-align: right;
+}
+
+.funkycommerce-native-video-hero--glow .funkycommerce-native-video-hero-overlay {
+ background: radial-gradient(circle at center, rgb(124 58 237 / 70%), #000 75%);
+}
+
+.funkycommerce-native-video-hero--fullbleed {
+ left: 50%;
+ width: 100vw;
+ margin-inline: -50vw;
+ border-radius: 0;
+}
+
+.funkycommerce-native-video-hero--fullbleed .funkycommerce-native-video-hero-content {
+ width: min(100%, var(--wp--style--global--wide-size, 1200px));
+ margin-inline: auto;
+ padding-inline: clamp(1rem, 3vw, 2rem);
+}
+
+.funkycommerce-native-video-hero--fullbleed .funkycommerce-native-video-hero-controls {
+ right: max(1.25rem, calc((100vw - var(--wp--style--global--wide-size, 1200px)) / 2 + 1.25rem));
+}
+
+.funkycommerce-native-video-hero--split {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
+ background: #fff;
+ color: #18181b;
+}
+
+.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-content {
+ order: -1;
+ align-self: center;
+ padding-right: 2rem;
+}
+
+.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-media,
+.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-poster,
+.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-overlay {
+ left: 50%;
+ width: 50%;
+}
+
+.funkycommerce-native-video-hero--minimal {
+ background: #fff;
+ color: #18181b;
+ text-align: center;
+ justify-content: center;
+}
+
+.funkycommerce-native-video-hero--minimal :is(.funkycommerce-native-video-hero-media, .funkycommerce-native-video-hero-poster) {
+ opacity: 0.15;
+}
+
+.funkycommerce-native-video-hero--strip {
+ min-height: 0 !important;
+ padding-block: 1.5rem;
+}
+
+@media (max-width: 640px) {
+ .funkycommerce-native-video-hero--split {
+ grid-template-columns: 1fr;
+ }
+
+ .funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-media,
+ .funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-poster,
+ .funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-overlay {
+ left: 0;
+ width: 100%;
+ }
+}
diff --git a/assets/dist/theme.css b/assets/dist/theme.css
new file mode 100644
index 0000000..1dfd03c
--- /dev/null
+++ b/assets/dist/theme.css
@@ -0,0 +1 @@
+*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }:root{--sf-shell-line:color-mix(in srgb,currentColor 13%,transparent);--sf-shell-surface:color-mix(in srgb,var(--wp--preset--color--surface) 88%,transparent);--sf-shell-radius:var(--wp--custom--shell--radius,18px);--sf-shell-shadow:0 24px 70px -48px rgba(32,24,48,.45)}html{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeLegibility}:where(a,button,input,textarea,select):focus-visible{outline:2px solid var(--wp--preset--color--accent);outline-offset:3px}::-moz-selection{background:var(--wp--preset--color--accent-soft);color:var(--wp--preset--color--foreground)}::selection{background:var(--wp--preset--color--accent-soft);color:var(--wp--preset--color--foreground)}.sf-skip-link{background:var(--wp--preset--color--foreground);border-radius:999px;color:var(--wp--preset--color--background);font-size:.8rem;font-weight:650;left:1rem;padding:.65rem 1rem;position:fixed;top:1rem;transform:translateY(-6rem);transition:transform .16s ease;z-index:1000}.sf-skip-link:focus{transform:translateY(0)}.sf-shell-header{backdrop-filter:blur(18px) saturate(140%);background:var(--sf-shell-surface);border-bottom:1px solid var(--sf-shell-line);position:sticky;top:var(--wp-admin--admin-bar--height,0);z-index:50}.sf-shell-header__inner{min-height:5.25rem;padding-block:1rem}.sf-shell-identity{min-width:0}.sf-shell-identity .wp-block-site-logo{flex:0 0 auto}.sf-shell-identity .custom-logo{border-radius:11px}.sf-shell-site-title{font-family:var(--wp--preset--font-family--display);font-size:clamp(1.05rem,2vw,1.25rem);font-weight:680;letter-spacing:-.035em;line-height:1.1}.sf-shell-site-title a{color:inherit;text-decoration:none}.sf-shell-tagline{color:var(--wp--preset--color--muted);font-size:.72rem;line-height:1.3;margin-top:.25rem}.sf-shell-navigation{font-size:.84rem;font-weight:580}.sf-shell-navigation .wp-block-navigation-item__content{border-radius:999px;color:inherit;padding:.55rem .75rem;text-decoration:none;transition:background-color .16s ease,color .16s ease}.sf-shell-navigation .current-menu-item>.wp-block-navigation-item__content,.sf-shell-navigation .wp-block-navigation-item__content:hover{background:var(--wp--preset--color--accent-soft);color:var(--wp--preset--color--accent)}.sf-shell-navigation .wp-block-navigation__responsive-container.is-menu-open{background:var(--wp--preset--color--background);color:var(--wp--preset--color--foreground);padding:2rem}.sf-shell-main{min-height:55vh}.sf-shell-hero{border-bottom:1px solid var(--sf-shell-line);overflow:hidden;position:relative}.sf-shell-hero:before{background:radial-gradient(circle at 15% 25%,color-mix(in srgb,var(--wp--preset--color--accent) 18%,transparent),transparent 38%),radial-gradient(circle at 85% 10%,color-mix(in srgb,var(--wp--preset--color--highlight) 25%,transparent),transparent 34%);content:"";inset:0;pointer-events:none;position:absolute}.sf-shell-hero__inner{padding-block:clamp(5rem,13vw,10rem);position:relative}.sf-shell-eyebrow{color:var(--wp--preset--color--accent);font-size:.72rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase}.sf-shell-hero .wp-block-site-title{font-size:clamp(3rem,10vw,7.5rem);font-weight:680;letter-spacing:-.07em;line-height:.9;max-width:11ch}.sf-shell-hero .wp-block-site-tagline{color:var(--wp--preset--color--muted);font-size:clamp(1.05rem,2vw,1.35rem);line-height:1.5;max-width:42rem}.sf-shell-content{padding-block:clamp(3.5rem,8vw,7rem)}.sf-shell-card{background:var(--wp--preset--color--surface);border:1px solid var(--sf-shell-line);border-radius:var(--sf-shell-radius);box-shadow:var(--sf-shell-shadow);height:100%;overflow:hidden;transition:border-color .18s ease,transform .18s ease}.sf-shell-card:hover{border-color:color-mix(in srgb,var(--wp--preset--color--accent) 34%,transparent);transform:translateY(-3px)}.sf-shell-card .wp-block-post-featured-image{margin:0}.sf-shell-card__body{padding:clamp(1.25rem,3vw,2rem)}.sf-shell-card .wp-block-post-title a{color:inherit;text-decoration:none}.sf-shell-meta{color:var(--wp--preset--color--muted);font-size:.75rem;letter-spacing:.02em}.sf-shell-empty{background:var(--wp--preset--color--surface);border:1px dashed var(--sf-shell-line);border-radius:var(--sf-shell-radius);padding:clamp(2rem,6vw,4rem);text-align:center}.sf-shell-footer{background:var(--wp--preset--color--foreground);color:var(--wp--preset--color--background)}.sf-shell-footer a{color:inherit}.sf-shell-footer__inner{gap:2rem;padding-block:clamp(3rem,7vw,5rem)}.sf-shell-footer__brand{max-width:28rem}.sf-shell-footer__brand .wp-block-site-tagline,.sf-shell-footer__meta{color:color-mix(in srgb,var(--wp--preset--color--background) 62%,transparent)}.sf-shell-footer__navigation{font-size:.82rem}.sf-shell-footer__navigation .wp-block-navigation-item__content{text-decoration:none}.sf-shell-footer__meta{border-top:1px solid color-mix(in srgb,var(--wp--preset--color--background) 15%,transparent);padding-block:1.25rem 2rem}.sticky{position:sticky}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.border{border-width:1px}@media (max-width:600px){.sf-shell-tagline{display:none}.sf-shell-header__inner{min-height:4.5rem}}@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,:after,:before{scroll-behavior:auto!important;transition-duration:.01ms!important}}.funkycommerce-native-video-hero{position:relative;display:flex;overflow:hidden;align-items:flex-end;min-height:24rem;padding:clamp(2rem,6vw,5rem);border-radius:1.5rem;background:#09090b;color:#fff}.funkycommerce-native-video-hero-media,.funkycommerce-native-video-hero-overlay,.funkycommerce-native-video-hero-poster{position:absolute;inset:0;width:100%;height:100%;border:0;-o-object-fit:cover;object-fit:cover}.funkycommerce-native-video-hero-overlay{background:#000}.funkycommerce-native-video-hero-content{position:relative;z-index:1;max-width:48rem}.funkycommerce-native-video-hero-controls{position:absolute;right:1.25rem;bottom:1.25rem;z-index:2;display:flex;gap:.5rem}.funkycommerce-native-video-hero-control{display:inline-grid;width:2.75rem;height:2.75rem;place-items:center;border:0;border-radius:9999px;background:rgba(0,0,0,.6);color:#fff;cursor:pointer}.funkycommerce-native-video-hero--center{justify-content:center;text-align:center}.funkycommerce-native-video-hero--right{justify-content:flex-end;text-align:right}.funkycommerce-native-video-hero--glow .funkycommerce-native-video-hero-overlay{background:radial-gradient(circle at center,rgba(124,58,237,.7),#000 75%)}.funkycommerce-native-video-hero--fullbleed{left:50%;width:100vw;margin-inline:-50vw;border-radius:0}.funkycommerce-native-video-hero--fullbleed .funkycommerce-native-video-hero-content{width:min(100%,var(--wp--style--global--wide-size,1200px));margin-inline:auto;padding-inline:clamp(1rem,3vw,2rem)}.funkycommerce-native-video-hero--fullbleed .funkycommerce-native-video-hero-controls{right:max(1.25rem,calc((100vw - var(--wp--style--global--wide-size, 1200px))/2 + 1.25rem))}.funkycommerce-native-video-hero--split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);background:#fff;color:#18181b}.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-content{order:-1;align-self:center;padding-right:2rem}.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-media,.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-overlay,.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-poster{left:50%;width:50%}.funkycommerce-native-video-hero--minimal{background:#fff;color:#18181b;text-align:center;justify-content:center}.funkycommerce-native-video-hero--minimal :is(.funkycommerce-native-video-hero-media,.funkycommerce-native-video-hero-poster){opacity:.15}.funkycommerce-native-video-hero--strip{min-height:0!important;padding-block:1.5rem}@media (max-width:640px){.funkycommerce-native-video-hero--split{grid-template-columns:1fr}.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-media,.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-overlay,.funkycommerce-native-video-hero--split .funkycommerce-native-video-hero-poster{left:0;width:100%}}
\ No newline at end of file
diff --git a/assets/dist/theme.js b/assets/dist/theme.js
new file mode 100644
index 0000000..6c7df37
--- /dev/null
+++ b/assets/dist/theme.js
@@ -0,0 +1 @@
+(()=>{function p(){var s,u,l,m,y,f;let n=document.querySelector("[data-funky-spotify-embed]");if(!n)return;let e=((u=(s=window.FunkyCommerceThemeSettings)==null?void 0:s.spotify)==null?void 0:u.embedUrl)||"",i=((m=(l=window.FunkyCommerceThemeSettings)==null?void 0:l.spotify)==null?void 0:m.title)||"",r=((f=(y=window.FunkyCommerceThemeSettings)==null?void 0:y.spotify)==null?void 0:f.description)||"",c=n.dataset.funkySpotifyEmbed||e,t;try{t=new URL(c)}catch(a){return}if(t.protocol!=="https:"||t.hostname!=="open.spotify.com"||!/^\/embed\/(?:track|album|playlist|artist|show|episode)\/[A-Za-z0-9]{10,64}$/.test(t.pathname)||n.querySelector("iframe"))return;let o=document.createElement("iframe");o.title=i||"Spotify player",o.src=t.href,o.loading="lazy",o.allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture",o.referrerPolicy="strict-origin-when-cross-origin",o.style.border="0",o.style.borderRadius="12px",o.style.height="352px",o.style.width="100%";let d=[];if(i){let a=document.createElement("h2");a.textContent=i,d.push(a)}if(r){let a=document.createElement("p");a.textContent=r,d.push(a)}d.push(o),n.replaceChildren(...d),n.hidden=!1}p();function v(){document.querySelectorAll(".funkycommerce-native-video-hero").forEach(n=>{let e=n.querySelector(".funkycommerce-native-video-hero-media"),i=n.querySelector(".funkycommerce-native-video-hero-playback"),r=n.querySelector(".funkycommerce-native-video-hero-mute");if(!e||!i||!r)return;let c=()=>{let t=n.dataset.videoMuted==="true";if(e instanceof HTMLVideoElement){e.muted=t;return}if(!(e instanceof HTMLIFrameElement)||!e.contentWindow)return;let o=n.dataset.videoProvider==="youtube"?{event:"command",func:t?"mute":"unMute",args:[]}:{method:"setVolume",value:t?0:1};e.contentWindow.postMessage(JSON.stringify(o),"*"),!t&&n.dataset.videoProvider==="youtube"&&e.contentWindow.postMessage(JSON.stringify({event:"command",func:"playVideo",args:[]}),"*")};e instanceof HTMLIFrameElement&&e.addEventListener("load",c),c(),i.addEventListener("click",()=>{let t=n.dataset.videoPlaying==="true";e instanceof HTMLVideoElement?t?e.pause():e.play():e instanceof HTMLIFrameElement&&(t?(e.dataset.videoSrc=e.src,e.removeAttribute("src")):e.dataset.videoSrc&&(e.src=e.dataset.videoSrc));let o=!t;n.dataset.videoPlaying=String(o),i.textContent=o?"\u275A\u275A":"\u25B6",i.setAttribute("aria-label",o?"Pause background video":"Play background video")}),r.addEventListener("click",()=>{let t=n.dataset.videoMuted!=="true";n.dataset.videoMuted=String(t),c(),r.textContent=t?"\u{1F507}":"\u{1F50A}",r.setAttribute("aria-label",t?"Unmute background video":"Mute background video")})})}v();})();
diff --git a/assets/js/theme.js b/assets/js/theme.js
new file mode 100644
index 0000000..9791cf4
--- /dev/null
+++ b/assets/js/theme.js
@@ -0,0 +1,112 @@
+function mountSpotifyPlayer() {
+ const slot = document.querySelector("[data-funky-spotify-embed]");
+ if (!slot) return;
+
+ const localizedUrl = window.FunkyCommerceThemeSettings?.spotify?.embedUrl || "";
+ const title = window.FunkyCommerceThemeSettings?.spotify?.title || "";
+ const description = window.FunkyCommerceThemeSettings?.spotify?.description || "";
+ const candidate = slot.dataset.funkySpotifyEmbed || localizedUrl;
+ let embedUrl;
+ try {
+ embedUrl = new URL(candidate);
+ } catch {
+ return;
+ }
+ if (
+ embedUrl.protocol !== "https:" ||
+ embedUrl.hostname !== "open.spotify.com" ||
+ !/^\/embed\/(?:track|album|playlist|artist|show|episode)\/[A-Za-z0-9]{10,64}$/.test(embedUrl.pathname)
+ ) {
+ return;
+ }
+ if (slot.querySelector("iframe")) return;
+
+ const iframe = document.createElement("iframe");
+ iframe.title = title || "Spotify player";
+ iframe.src = embedUrl.href;
+ iframe.loading = "lazy";
+ iframe.allow = "autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture";
+ iframe.referrerPolicy = "strict-origin-when-cross-origin";
+ iframe.style.border = "0";
+ iframe.style.borderRadius = "12px";
+ iframe.style.height = "352px";
+ iframe.style.width = "100%";
+ const content = [];
+ if (title) {
+ const heading = document.createElement("h2");
+ heading.textContent = title;
+ content.push(heading);
+ }
+ if (description) {
+ const copy = document.createElement("p");
+ copy.textContent = description;
+ content.push(copy);
+ }
+ content.push(iframe);
+ slot.replaceChildren(...content);
+ slot.hidden = false;
+}
+
+mountSpotifyPlayer();
+
+function mountVideoHeroControls() {
+ document.querySelectorAll(".funkycommerce-native-video-hero").forEach((hero) => {
+ const media = hero.querySelector(".funkycommerce-native-video-hero-media");
+ const playbackControl = hero.querySelector(".funkycommerce-native-video-hero-playback");
+ const muteControl = hero.querySelector(".funkycommerce-native-video-hero-mute");
+ if (!media || !playbackControl || !muteControl) return;
+
+ const applyMuted = () => {
+ const muted = hero.dataset.videoMuted === "true";
+ if (media instanceof HTMLVideoElement) {
+ media.muted = muted;
+ return;
+ }
+ if (!(media instanceof HTMLIFrameElement) || !media.contentWindow) return;
+ const message = hero.dataset.videoProvider === "youtube"
+ ? { event: "command", func: muted ? "mute" : "unMute", args: [] }
+ : { method: "setVolume", value: muted ? 0 : 1 };
+ media.contentWindow.postMessage(JSON.stringify(message), "*");
+ if (!muted && hero.dataset.videoProvider === "youtube") {
+ media.contentWindow.postMessage(
+ JSON.stringify({ event: "command", func: "playVideo", args: [] }),
+ "*",
+ );
+ }
+ };
+
+ if (media instanceof HTMLIFrameElement) {
+ media.addEventListener("load", applyMuted);
+ }
+ applyMuted();
+
+ playbackControl.addEventListener("click", () => {
+ const playing = hero.dataset.videoPlaying === "true";
+ if (media instanceof HTMLVideoElement) {
+ if (playing) media.pause();
+ else void media.play();
+ } else if (media instanceof HTMLIFrameElement) {
+ if (playing) {
+ media.dataset.videoSrc = media.src;
+ media.removeAttribute("src");
+ } else if (media.dataset.videoSrc) {
+ media.src = media.dataset.videoSrc;
+ }
+ }
+ const next = !playing;
+ hero.dataset.videoPlaying = String(next);
+ playbackControl.textContent = next ? "\u275A\u275A" : "\u25B6";
+ playbackControl.setAttribute("aria-label", next ? "Pause background video" : "Play background video");
+ });
+
+ muteControl.addEventListener("click", () => {
+ const next = hero.dataset.videoMuted !== "true";
+ hero.dataset.videoMuted = String(next);
+ applyMuted();
+ muteControl.textContent = next ? "\uD83D\uDD07" : "\uD83D\uDD0A";
+ muteControl.setAttribute("aria-label", next ? "Unmute background video" : "Mute background video");
+ });
+ });
+}
+
+mountVideoHeroControls();
diff --git a/assets/storefront-ui-strings/en.json b/assets/storefront-ui-strings/en.json
new file mode 100644
index 0000000..279e346
--- /dev/null
+++ b/assets/storefront-ui-strings/en.json
@@ -0,0 +1,677 @@
+{
+ "cart.title": "Your cart",
+ "cart.aria": "Shopping cart",
+ "cart.close": "Close cart",
+ "cart.empty.heading": "Your cart is empty",
+ "cart.empty.body": "Looks like you haven't added anything yet.",
+ "cart.empty.body_alt": "Add products from the shop to see them here.",
+ "cart.you_might_like": "You might like",
+ "cart.subtotal": "Subtotal",
+ "cart.shipping_notice": "Shipping and taxes calculated at checkout.",
+ "cart.view_cart": "View cart",
+ "cart.checkout": "Checkout",
+ "cart.continue_shopping": "Continue shopping",
+ "cart.add": "Add to cart",
+ "cart.added": "Added ✓",
+ "cart.remove": "Remove",
+ "cart.ready": "Ready to check out?",
+ "cart.item_singular": "item",
+ "cart.item_plural": "items",
+ "cart.continue_checkout": "Continue to checkout",
+ "cart.shipping": "Shipping",
+ "cart.tax": "Tax",
+ "cart.digital_delivery": "Digital delivery",
+ "cart.free": "Free",
+ "cart.shipping_destination": "Shipping destination preview",
+ "cart.shipping_destination_aria": "Preview shipping destination",
+ "cart.empty.cart_title": "Your cart is empty",
+ "checkout.title": "Checkout",
+ "checkout.progress_aria": "Checkout progress",
+ "checkout.step.cart": "Cart",
+ "checkout.step.checkout": "Checkout",
+ "checkout.step.confirmation": "Confirmation",
+ "checkout.billing.title": "Billing details",
+ "checkout.field.first_name": "First name",
+ "checkout.field.last_name": "Last name",
+ "checkout.field.company": "Company name",
+ "checkout.field.country": "Country / region",
+ "checkout.field.address1": "Street address",
+ "checkout.field.address1_helper": "House number and street name",
+ "checkout.field.address2": "Apartment, suite, unit etc.",
+ "checkout.field.optional": "Optional",
+ "checkout.field.city": "Town / city",
+ "checkout.field.state": "State / county",
+ "checkout.field.postcode": "Postcode / ZIP",
+ "checkout.field.phone": "Phone",
+ "checkout.field.email": "Email address",
+ "checkout.field.phone_error": "Phone is not a valid phone number",
+ "checkout.field.email_error": "Email is not a valid email address",
+ "checkout.field.country_placeholder": "Select a country…",
+ "checkout.digital_notice": "Digital delivery. Your purchase is delivered instantly via the website after payment — no shipping address or shipping method needed.",
+ "checkout.account.title": "Account",
+ "checkout.account.create": "Create an account?",
+ "checkout.field.username": "Username",
+ "checkout.field.password": "Password",
+ "checkout.marketing_consent": "Keep me posted about new drops, offers, and restocks by email.",
+ "checkout.guest_notice": "An account will be created with your email address during checkout.",
+ "checkout.delivery.title": "Delivery",
+ "checkout.ship_to_different": "Ship to a different address?",
+ "checkout.shipping_method": "Shipping method",
+ "checkout.shipping_loading": "Loading live shipping methods…",
+ "checkout.subtotal": "Subtotal",
+ "checkout.discount": "Discount",
+ "checkout.shipping": "Shipping",
+ "checkout.tax": "Tax",
+ "checkout.free": "Free",
+ "checkout.free_shipping_nudge": "left to free standard shipping — back to cart",
+ "checkout.back_to_cart": "back to cart",
+ "checkout.order_notes.title": "Order notes",
+ "checkout.order_notes.label": "Notes about your order",
+ "checkout.order_notes.helper": "Optional — e.g. delivery instructions",
+ "checkout.coupon.title": "Have a coupon?",
+ "checkout.coupon.label": "Coupon code",
+ "checkout.coupon.apply": "Apply code",
+ "checkout.coupon.applying": "Applying…",
+ "checkout.coupon.applied": "Coupon “{code}” applied",
+ "checkout.coupon.remove": "Remove",
+ "checkout.coupon.error": "Failed to apply coupon",
+ "checkout.payment.title": "Payment method",
+ "checkout.payment.online": "Pay online",
+ "checkout.payment.online_desc": "Cards and other Stripe-supported online payment methods.",
+ "checkout.payment.blik": "BLIK",
+ "checkout.payment.blik_desc": "Pay instantly in PLN with a 6-digit BLIK code via Stripe.",
+ "checkout.payment.blik_label": "BLIK code",
+ "checkout.payment.blik_error": "BLIK code must be 6 digits",
+ "checkout.payment.blik_expired": "Code expired — place the order again to get a fresh authorization window.",
+ "checkout.payment.bacs": "Direct bank transfer",
+ "checkout.payment.bacs_desc": "Pay from your bank using the store's transfer instructions after placing the order.",
+ "checkout.payment.instructions": "Payment instructions",
+ "checkout.payment.cod": "Payment upon delivery",
+ "checkout.payment.cod_desc": "Pay in cash when your order arrives.",
+ "checkout.payment.cheque": "Bank cheque",
+ "checkout.payment.cheque_desc": "Mail a cheque — your order ships once it clears.",
+ "checkout.payment.unavailable_digital": "Not available for digital orders",
+ "checkout.payment.unavailable": "Not available at this time",
+ "checkout.payment.ssl": "Transactions secured with SSL encryption",
+ "checkout.payment.unavailable_backend": "Crypto checkout is not currently available on this backend.",
+ "checkout.payment.method_unavailable": "The selected payment method is not currently available.",
+ "checkout.summary.title": "Review order",
+ "checkout.cta": "Place order",
+ "checkout.terms": "I have read and agree to the website's terms and conditions",
+ "checkout.privacy": "I consent to my personal data being processed as described in the privacy policy",
+ "checkout.terms_link": "terms and conditions",
+ "checkout.privacy_link": "privacy policy",
+ "checkout.error.billing_required": "Complete the required billing details before placing the order.",
+ "checkout.error.method_unavailable": "Selected payment method is unavailable.",
+ "order_success.breadcrumb": "Order confirmed",
+ "order_success.section": "Order confirmation",
+ "order_success.section_digital": "Digital order confirmation",
+ "order_success.heading": "Order successful",
+ "order_success.thank_you": "Thank you, {name} — your order #{number} is confirmed. A receipt has been sent to your email.",
+ "order_success.thank_you_digital": "Thank you, {name} — your digital order #{number} is confirmed. Your downloads are ready below and a receipt has been sent to your email.",
+ "order_success.customer_fallback": "there",
+ "order_success.empty.heading": "No completed order found",
+ "order_success.empty.message": "This page shows the most recent order completed in this browser session.",
+ "order_success.refreshing": "Refreshing order details",
+ "order_success.refresh_error": "Live details could not be refreshed. Showing the order captured at checkout.",
+ "order_success.order_label": "Order #{number}",
+ "order_success.quantity": "Quantity: {quantity}",
+ "order_success.shipping_to": "Shipping to",
+ "order_success.delivery": "Delivery",
+ "order_success.details.order": "Order details",
+ "order_success.details.delivery_payment": "Delivery and payment",
+ "order_success.receipt": "Receipt",
+ "order_success.currency": "Currency",
+ "order_success.account_login_error": "Your order was created, but automatic account setup did not finish: {error} Sign in and check My Orders; contact support if the order is missing.",
+ "order_success.delivery_note": "Your order is now in the store workflow.",
+ "order_success.native_note": "Continue to the native order page for the latest payment and status details.",
+ "order_success.payment.cod": "Cash on delivery",
+ "order_success.payment.cheque": "Cheque payment",
+ "order_success.payment.bacs": "Direct bank transfer",
+ "order_success.payment.crypto": "Cryptocurrency payment",
+ "order_success.payment.blik": "BLIK",
+ "order_success.payment.card": "Card payment",
+ "order_success.row.subtotal": "Subtotal",
+ "order_success.row.discount": "Discount",
+ "order_success.row.discount_with_codes": "Discount ({codes})",
+ "order_success.row.shipping": "Shipping",
+ "order_success.row.tax": "Tax",
+ "order_success.row.free": "Free",
+ "order_success.row.digital_delivery": "Digital delivery",
+ "order_success.row.total": "Total paid",
+ "order_success.cta.shopping": "Continue shopping",
+ "order_success.cta.native": "Open native order page",
+ "order_success.cta.track": "Track my order",
+ "order_success.cta.orders": "View my orders",
+ "order_success.cta.private": "Open private order page",
+ "order_success.cta.pdf": "Save receipt as PDF",
+ "order_success.cta.pdf_hint": "Opens your browser print dialog, where you can save this receipt as a PDF.",
+ "order_success.cta.help": "Get order help",
+ "order_success.promo": "Thanks for being a returning customer — here's 15% off your next order.",
+ "order_success.support": "Questions about your order?",
+ "order_success.contact": "Contact us",
+ "order_success.downloads": "Your Downloads",
+ "order_success.downloads_note": "Files are also available anytime from your account's Downloads tab — no need to save this page.",
+ "order_success.download.available": "Available",
+ "order_success.download.unavailable": "Unavailable",
+ "order_success.download.expires": "Expires:",
+ "order_success.download.remaining": "Downloads remaining:",
+ "order_success.download.unlimited": "Unlimited",
+ "order_success.download.cta": "Download",
+ "order_success.support.trouble": "Trouble accessing a download?",
+ "order_status.pending": "Pending payment",
+ "order_status.processing": "Processing",
+ "order_status.on-hold": "On hold",
+ "order_status.completed": "Completed",
+ "order_status.cancelled": "Cancelled",
+ "order_status.failed": "Failed",
+ "order_status.refunded": "Refunded",
+ "order_details.private_title": "Private order",
+ "order_details.heading": "Order #{number}",
+ "order_details.guest_expires": "This private guest link expires {date}.",
+ "order_details.loading": "Loading order",
+ "order_details.unavailable": "Order not found, unavailable, or the 24-hour guest link has expired.",
+ "order_details.back_account": "Back to account",
+ "auth.title.login": "Welcome back",
+ "auth.title.register": "Create your account",
+ "auth.title.forgot": "Forgotten password",
+ "auth.breadcrumb.login": "Sign in",
+ "auth.breadcrumb.register": "Register",
+ "auth.breadcrumb.forgot": "Forgot password",
+ "auth.desc.login": "Sign in to track orders, manage your wishlist, and check out faster.",
+ "auth.desc.register": "Join Superfunky for personalized recommendations and faster checkout.",
+ "auth.desc.forgot": "We'll email you a secure link to get back into your account.",
+ "auth.tab.login": "Login",
+ "auth.tab.register": "Register",
+ "auth.tab.forgot": "Forgot",
+ "auth.password_updated": "Your password was updated. Sign in with the new password.",
+ "auth.or_continue": "Or continue with",
+ "auth.continue_with": "Continue with {provider}",
+ "auth.brand_tagline": "A modern storefront experience connected to your site account.",
+ "auth.login.username": "Username or email",
+ "auth.login.password": "Password",
+ "auth.login.remember": "Remember me",
+ "auth.login.forgot_link": "Forgot password?",
+ "auth.login.error": "Sign-in failed.",
+ "auth.login.cta": "Sign in",
+ "auth.login.cta_loading": "Signing in…",
+ "auth.register.first_name": "First name",
+ "auth.register.last_name": "Last name",
+ "auth.register.username": "Username",
+ "auth.register.username_placeholder": "e.g. funk_rider.99",
+ "auth.register.username_helper": "Used for your community profile URL — letters, numbers, underscores, hyphens, dots",
+ "auth.register.email": "Email",
+ "auth.register.password": "Password",
+ "auth.register.password_helper": "At least 8 characters",
+ "auth.register.confirm": "Confirm password",
+ "auth.register.newsletter": "I'd like to receive occasional emails about new drops, offers, and restocks. You can unsubscribe anytime.",
+ "auth.register.error": "The account could not be created.",
+ "auth.register.cta": "Create account",
+ "auth.register.cta_loading": "Creating account…",
+ "auth.register.success.heading": "Account created.",
+ "auth.register.success.body": "You can now sign in with your email and password.",
+ "auth.register.success.cta": "Continue to sign in",
+ "auth.forgot.field": "Username or email",
+ "auth.forgot.error": "The reset email could not be sent.",
+ "auth.forgot.cta": "Send reset link",
+ "auth.forgot.cta_loading": "Sending…",
+ "auth.forgot.success": "If an account exists for {identity}, a reset link is on its way.",
+ "auth.reset.breadcrumb": "Reset password",
+ "auth.reset.error.mismatch": "The passwords do not match.",
+ "auth.reset.error.failed": "The password could not be reset.",
+ "auth.oauth.error": "Provider sign-in failed.",
+ "auth.oauth.callback_error": "The provider callback is incomplete or invalid.",
+ "validation.passwords_mismatch": "Passwords don't match",
+ "validation.password.min_length": "Use at least 8 characters.",
+ "validation.password.uppercase": "Include an uppercase letter.",
+ "validation.password.lowercase": "Include a lowercase letter.",
+ "validation.password.number": "Include a number.",
+ "validation.password.special": "Include a special character.",
+ "validation.required": "{label} is required.",
+ "validation.min_length": "{label} must be at least {min} characters.",
+ "validation.max_length": "{label} must be at most {max} characters.",
+ "validation.email": "{label} is not a valid email address.",
+ "validation.phone": "{label} is not a valid phone number.",
+ "validation.username_chars": "{label} may only contain letters, numbers, underscores, hyphens, and dots.",
+ "account.tab.dashboard": "Dashboard",
+ "account.tab.orders": "Orders",
+ "account.tab.addresses": "Addresses",
+ "account.tab.community": "Community",
+ "account.logout": "Log out",
+ "account.signin_register": "Sign in or register",
+ "account.guest.name": "Guest account",
+ "account.guest.email_placeholder": "Sign in to load your account",
+ "account.loading": "Loading your account",
+ "account.empty_state": "Sign in to load your profile and account summary.",
+ "account.greeting": "Hi, {name} 👋",
+ "account.role": "{role} role",
+ "account.verified": "Verified account",
+ "account.orders.empty": "No orders yet.",
+ "account.orders.loading": "Loading your orders…",
+ "account.orders.sign_in": "Sign in to view your order history.",
+ "account.orders.view_details": "View details",
+ "account.addresses.loading": "Loading your saved addresses…",
+ "account.addresses.sign_in": "Sign in to manage billing and shipping addresses.",
+ "account.addresses.save": "Save address",
+ "account.addresses.save_error": "The address could not be saved.",
+ "account.guest.benefit1_eyebrow": "Your personal storefront",
+ "account.guest.benefit1_title": "Bring your account experience together",
+ "account.guest.benefit2_eyebrow": "Private order history",
+ "account.guest.benefit2_title": "Track every purchase in one place",
+ "account.guest.benefit3_eyebrow": "Faster checkout",
+ "account.guest.benefit3_title": "Save billing and shipping details",
+ "account.guest.benefit4_eyebrow": "Community and marketplace",
+ "account.guest.benefit4_title": "Unlock the tools assigned to your role",
+ "account.guest.cta.login": "Log in",
+ "account.guest.cta.register": "Create an account",
+ "product.add_to_cart": "Add to cart",
+ "product.added": "Added ✓",
+ "product.buy_now": "Buy now",
+ "product.choose_options": "Choose available options",
+ "product.select_options": "Select options",
+ "product.grouped": "Grouped product",
+ "product.add_wishlist": "Add to wishlist",
+ "product.remove_wishlist": "Remove from wishlist",
+ "product.save_reading": "Save to reading list",
+ "product.remove_reading": "Remove from reading list",
+ "product.unavailable": "Product unavailable",
+ "product.loading": "Loading product",
+ "product.related_loading": "Loading related products",
+ "search.placeholder": "Search products, stories, people, and tags…",
+ "search.open": "Open search",
+ "search.close": "Close search",
+ "header.theme.toggle": "Toggle color mode",
+ "header.theme.light": "Switch to light mode",
+ "header.theme.dark": "Switch to dark mode",
+ "header.push.enable": "Enable push notifications",
+ "header.push.disable": "Disable push notifications",
+ "header.push.enabled": "Push notifications enabled",
+ "header.account": "Account",
+ "header.reading_list": "Reading list",
+ "header.wishlist": "Wishlist",
+ "header.cart": "Cart",
+ "header.sync_error": "{label} (sync error)",
+ "header.sync_error_detail": "{label} — {message}",
+ "header.menu.open": "Open menu",
+ "header.menu.site": "Site menu",
+ "header.menu.title": "Menu",
+ "header.menu.close": "Close menu",
+ "header.navigation.main": "Main navigation",
+ "header.navigation.mobile": "Mobile navigation",
+ "search.aria": "Search results",
+ "search.results": "Search results",
+ "search.loading": "Searching the site…",
+ "search.no_results": "No results for “{query}”",
+ "search.unavailable": "Search is unavailable",
+ "search.type.product": "Product",
+ "search.type.post": "Post",
+ "search.type.page": "Page",
+ "search.type.post_category": "Post category",
+ "search.type.post_tag": "Post tag",
+ "search.type.product_category": "Product category",
+ "search.type.product_tag": "Product tag",
+ "search.type.product_brand": "Product brand",
+ "search.type.author": "Author",
+ "search.type.community_post": "Community post",
+ "search.type.community_author": "Community member",
+ "search.type.community_tag": "Community tag",
+ "search.group.catalog": "Catalog",
+ "search.group.editorial": "Editorial",
+ "search.group.community": "Community",
+ "search.group.pages": "Pages",
+ "nav.home": "Home",
+ "nav.main_aria": "Main navigation",
+ "nav.mobile_aria": "Mobile navigation",
+ "nav.site_aria": "Site menu",
+ "nav.open_menu": "Open menu",
+ "nav.close_menu": "Close menu",
+ "nav.account": "Account",
+ "nav.cart": "Cart",
+ "nav.wishlist": "Wishlist",
+ "nav.reading_list": "Reading list",
+ "nav.toggle_dark": "Toggle dark mode",
+ "nav.select_currency": "Select currency",
+ "nav.select_language": "Select language",
+ "nav.close_newsletter": "Close newsletter signup",
+ "nav.close_quickview": "Close quick view",
+ "nav.breadcrumb": "Breadcrumb",
+ "image.close": "Close image viewer",
+ "image.next": "Next image",
+ "image.prev": "Previous image",
+ "image.viewer_aria": "Image viewer",
+ "cookie.title": "Cookie consent",
+ "cookie.settings": "Cookie settings",
+ "cookie.manage": "Manage cookie preferences",
+ "wishlist.loading": "Loading your saved products",
+ "wishlist.empty": "No listings yet.",
+ "reading_list.loading": "Loading your saved articles",
+ "reading_list.empty": "No articles yet.",
+ "community.share": "Share a new post",
+ "community.write": "Write a new article",
+ "community.list_product": "List a new product",
+ "community.profile.sections": "Profile sections",
+ "community.copy_link": "Copy link",
+ "loading.page": "Loading page",
+ "loading.content": "Loading content",
+ "loading.post": "Loading post",
+ "loading.product": "Loading product",
+ "loading.community_post": "Loading community post",
+ "loading.community_feed": "Loading community feed",
+ "loading.community_profile": "Loading community profile",
+ "error.page_unavailable": "Page unavailable",
+ "error.post_unavailable": "Post unavailable",
+ "error.product_unavailable": "Product unavailable",
+ "error.community_post_unavailable": "Community post unavailable",
+ "error.author_unavailable": "Author unavailable",
+ "error.archive_unavailable": "Archive unavailable",
+ "error.content_unavailable": "Content unavailable",
+ "error.like_failed": "The like could not be updated.",
+ "error.review_failed": "The review could not be submitted.",
+ "error.reply_failed": "The reply could not be submitted.",
+ "error.article_failed": "The article could not be published.",
+ "error.post_failed": "The post could not be published.",
+ "error.product_listing_failed": "The product could not be listed.",
+ "error.account_load": "The account could not be loaded",
+ "notification.dismiss": "Dismiss notification",
+ "notification.push.enable_error": "Couldn't enable push notifications.",
+ "notification.push.disable_error": "Couldn't disable push notifications.",
+ "newsletter.join": "Join the mailing list",
+ "newsletter.email": "Email",
+ "newsletter.email_placeholder": "you@example.com",
+ "newsletter.subscribe": "Subscribe",
+ "newsletter.subscribing": "Subscribing…",
+ "newsletter.subscribed": "Thank you. You are subscribed.",
+ "newsletter.signup_error": "The newsletter signup could not be saved.",
+ "newsletter.unsubscribe": "Unsubscribe",
+ "newsletter.consent": "Please accept the privacy note to continue.",
+ "newsletter.email_invalid": "Please enter a valid email address.",
+ "footer.newsletter.title": "Get product drops and offers first",
+ "footer.newsletter.description": "Subscribe for curated picks, launch alerts and monthly updates. No spam, unsubscribe anytime.",
+ "footer.newsletter.privacy": "I agree to receive marketing emails and accept the privacy policy.",
+ "footer.assistant.title": "AI shopping assistant — ask about sizing, orders, or recommendations",
+ "footer.assistant.tab": "AI shopping assistant",
+ "footer.spotify.tab": "Spotify player",
+ "footer.assistant_spotify.aria": "Assistant / Spotify",
+ "footer.radio.title": "Superfunky Radio",
+ "footer.radio.description": "Instrumental jazz-hop for browsing — swap for any track, album, or podcast link.",
+ "footer.nav.expand": "Expand {label}",
+ "footer.nav.collapse": "Collapse {label}",
+ "cookie.banner.accept_all": "Accept all",
+ "cookie.banner.description": "{providerName} uses cookies for the proper functioning of our website, as well as for analytics and advertising purposes. Learn more in our",
+ "cookie.banner.policy_link": "Cookies Policy",
+ "cookie.category.functional": "Functional",
+ "cookie.category.functional_desc": "Always on",
+ "cookie.category.functional_title": "Required for core features — cart, wishlist, and remembering this choice.",
+ "cookie.category.marketing": "Marketing",
+ "cookie.category.marketing_desc": "Ads",
+ "cookie.category.tracking": "Tracking",
+ "cookie.category.tracking_desc": "Analytics",
+ "cookie.category.performance": "Performance",
+ "cookie.category.performance_desc": "Layout",
+ "cookie.item.delete": "Delete",
+ "cookie.item.delete_aria": "Delete {name}",
+ "cookie.item.expires_in": "Expires in {lifetime}",
+ "cookie.item.required": "Required",
+ "cookie.manager.accept": "Accept",
+ "cookie.manager.close": "Close",
+ "cookie.manager.decline": "Decline",
+ "cookie.manager.nothing_to_show": "Nothing left to show.",
+ "cookie.manager.save": "Save",
+ "cookie.manager.subtitle": "Choose which optional cookies we may use.",
+ "cookie.manager.tab_cookies": "Cookies list",
+ "cookie.manager.tab_preferences": "Preferences",
+ "filters.all_authors": "All authors",
+ "filters.all_brands": "All brands",
+ "filters.all_categories": "All categories",
+ "filters.all_tags": "All tags",
+ "filters.aria_label": "{title} filters",
+ "filters.author_aria": "Filter by author",
+ "filters.brand_aria": "Filter by brand",
+ "filters.browse": "Browse",
+ "filters.category_aria": "Filter by category",
+ "filters.clear": "Clear filters",
+ "filters.default_empty_social": "No posts yet — be the first to share something here.",
+ "filters.default_title_posts": "Latest posts",
+ "filters.default_title_products": "Products",
+ "filters.default_title_social": "Community feed",
+ "filters.end_reached": "You've reached the end.",
+ "filters.interested_in": "Interested in",
+ "filters.layout_compact": "Compact grid (profile-style)",
+ "filters.layout_grid3": "3 columns",
+ "filters.layout_grid4": "4 columns",
+ "filters.layout_label": "Layout",
+ "filters.layout_list": "List",
+ "filters.layout_masonry": "Masonry",
+ "filters.load_mode_infinite": "Infinite scroll",
+ "filters.load_mode_pages": "Pages",
+ "filters.load_more": "Load more",
+ "filters.loading_more": "Loading more…",
+ "filters.next": "Next →",
+ "filters.no_posts_match": "No posts match these filters.",
+ "filters.no_products_match": "No products match these filters.",
+ "filters.pagination_aria": "{title} pagination",
+ "filters.prev": "← Prev",
+ "filters.rating_3": "3+ stars",
+ "filters.rating_4": "4+ stars",
+ "filters.rating_45": "4.5+ stars",
+ "filters.rating_any": "Any rating",
+ "filters.rating_aria": "Filter by minimum rating",
+ "filters.search_feed": "Search feed",
+ "filters.search_posts": "Search posts",
+ "filters.search_products": "Search products",
+ "filters.showing_label": "Showing",
+ "filters.showing_suffix": "of {total}",
+ "filters.sort_default_order": "Default order",
+ "filters.sort_featured": "Featured",
+ "filters.sort_feed_aria": "Sort feed",
+ "filters.sort_name": "Name",
+ "filters.sort_newest": "Newest",
+ "filters.sort_oldest": "Oldest",
+ "filters.sort_popular": "Most liked",
+ "filters.sort_posts_aria": "Sort posts",
+ "filters.sort_price_asc": "Price: low to high",
+ "filters.sort_price_desc": "Price: high to low",
+ "filters.sort_products_aria": "Sort products",
+ "filters.sort_rating": "Highest rated",
+ "filters.sort_title": "Title",
+ "filters.tag_aria": "Filter by tag",
+ "image.show_label": "Show {label}",
+ "newsletter.default_body": "Join our insider list for early access, private offers, and curated stories from the Superfunky world.",
+ "newsletter.default_title": "Be the first to know when the next favorite drops.",
+ "newsletter.eyebrow": "Stay in the loop",
+ "newsletter.image_placeholder_body": "Drop in a full-bleed product or editorial image here.",
+ "newsletter.image_placeholder_title": "Image placeholder",
+ "newsletter.maybe_later": "Maybe later",
+ "newsletter.subscribed_body": "Thanks for subscribing — expect a first look at our next drop and exclusive updates soon.",
+ "newsletter.trust.easy_unsubscribe": "Easy unsubscribe",
+ "newsletter.trust.no_spam": "No spam",
+ "newsletter.trust.privacy": "Privacy respected",
+ "order_success.download.downloading": "Downloading…",
+ "order_success.download.empty": "This order does not include any downloadable files.",
+ "order_success.download.error": "File download failed. Please try again or contact support.",
+ "order_success.download.loading": "Loading secure download links…",
+ "order_success.download.never": "Never",
+ "product.cta.gallery_thumbnails_aria": "{name} gallery thumbnails",
+ "product.cta.learn_more": "Learn more",
+ "product.cta.quick_view": "Quick view",
+ "product.cta.quick_view_aria": "Quick view — {name}",
+ "product.cta.show_photo_aria": "Show photo {index}",
+ "product.cta.view_product": "View product",
+ "product.cta.view_product_aria": "View {name}",
+ "product.cta.view_products": "View products",
+ "product.image_alt": "Product image",
+ "product.image_alt_indexed": "Product image {index}",
+ "product.status.available": "Available",
+ "product.status.new": "New",
+ "product.status.promotion": "Promotion",
+ "product.status.promotion_percent": "{percent}% promotion",
+ "product.status.sold_out": "Sold out",
+ "product.variation_unavailable.description": "Choose an in-stock option combination.",
+ "product.variation_unavailable.title": "Variation unavailable",
+ "reading_list.browse_blog": "Browse the blog",
+ "reading_list.cap_error": "Your reading-list limit could not be loaded: {error}",
+ "reading_list.cap_suffix": "of {cap}",
+ "reading_list.category_fallback": "Journal",
+ "reading_list.count": "{count} saved {item}",
+ "reading_list.empty_hint": "Use the bookmark button on any article in the blog to save it for later reading.",
+ "reading_list.item_plural": "articles",
+ "reading_list.item_singular": "article",
+ "reading_list.load_error": "Your saved articles could not be loaded: {message}",
+ "reading_list.mark_read": "Mark as read",
+ "reading_list.read": "Read",
+ "reading_list.reading_time": "{minutes} min read",
+ "reading_list.sign_in_suffix": "to sync your reading list across devices.",
+ "reading_list.sync_error": "Your reading list could not be synced: {error}",
+ "reading_list.sync_local": "persisted locally in this browser",
+ "reading_list.sync_synced": "synced to your account",
+ "reading_list.unread_title": "Unread",
+ "share.label": "Share",
+ "share.link_copied": "Link copied!",
+ "share.on.facebook": "Share on Facebook",
+ "share.on.linkedin": "Share on LinkedIn",
+ "share.on.telegram": "Share on Telegram",
+ "share.on.tiktok": "Share on TikTok",
+ "share.on.whatsapp": "Share on WhatsApp",
+ "share.on.x": "Share on X",
+ "share.via_email": "Share via email",
+ "wishlist.browse_shop": "Browse the shop",
+ "wishlist.cap_error": "Your wishlist limit could not be loaded: {error}",
+ "wishlist.cap_suffix": "of {cap}",
+ "wishlist.clear_unavailable": "Clear unavailable products",
+ "wishlist.count": "{count} saved {item}",
+ "wishlist.empty_hint": "Tap the heart icon on any product card to save it here for later — it stays saved across visits.",
+ "wishlist.item_plural": "products",
+ "wishlist.item_singular": "product",
+ "wishlist.load_error": "Your saved products could not be loaded: {message}",
+ "wishlist.sign_in_suffix": "to sync your wishlist across devices.",
+ "wishlist.sync_error": "Your wishlist could not be synced: {error}",
+ "wishlist.sync_local": "persisted locally in this browser",
+ "wishlist.sync_synced": "synced to your account",
+ "wishlist.unavailable_message": "These saved products are no longer available in the current catalog.",
+ "account.tab.downloads": "Downloads",
+ "account.avatar.saving": "Saving avatar…",
+ "account.avatar.change": "Change avatar",
+ "account.avatar.add": "Add avatar",
+ "account.avatar.remove": "Remove avatar",
+ "account.avatar.max_size": "Max file size 690KB",
+ "account.email.verification_required": "Email verification required",
+ "account.email.verification_optional": "Email verification optional",
+ "account.newsletter.subscribed": "Subscribed to newsletter",
+ "account.newsletter.not_subscribed": "Not subscribed to newsletter",
+ "account.stat.orders_placed": "Orders placed",
+ "account.stat.saved_addresses": "Saved addresses",
+ "account.stat.publishing_role": "Publishing role",
+ "account.role.member": "Member",
+ "account.guest.benefit1_description": "Sign in to see your verified profile and private customer tools.",
+ "account.guest.benefit1_item1": "Review your profile and account status",
+ "account.guest.benefit1_item2": "See orders and saved delivery details together",
+ "account.guest.benefit1_item3": "Discover publishing tools enabled for your role",
+ "account.guest.benefit2_description": "Your order history is private. Sign in to review real order statuses, totals, products, and variation details.",
+ "account.guest.benefit2_item1": "See current fulfilment status",
+ "account.guest.benefit2_item2": "Review line items and variations",
+ "account.guest.benefit2_item3": "Keep past purchases available for reference",
+ "account.guest.benefit3_description": "Create an account to securely manage your customer profile and checkout addresses.",
+ "account.guest.benefit3_item1": "Edit billing and shipping separately",
+ "account.guest.benefit3_item2": "Reuse accurate customer details",
+ "account.guest.benefit3_item3": "Keep address data private to your account",
+ "account.guest.benefit4_description": "Sign in to manage your public profile and access Creator, Collaborator, or administrator publishing actions when permitted.",
+ "account.guest.benefit4_item1": "Control public profile visibility",
+ "account.guest.benefit4_item2": "Publish community posts when eligible",
+ "account.guest.benefit4_item3": "List products and write articles when eligible",
+ "account.guest.benefit5_eyebrow": "Secure digital library",
+ "account.guest.benefit5_title": "Keep purchased files available",
+ "account.guest.benefit5_description": "Sign in to access the downloads available to your account.",
+ "account.guest.benefit5_item1": "Use signed download links",
+ "account.guest.benefit5_item2": "Review expiry and remaining limits",
+ "account.guest.benefit5_item3": "Keep purchases tied to your account",
+ "community.profile.unavailable": "Community profile unavailable",
+ "community.profile.private_badge": "Private",
+ "community.role.collaborator": "Collaborator",
+ "community.role.creator": "Creator",
+ "community.profile.own_badge": "That's you",
+ "community.follow.following": "Following",
+ "community.follow.requested": "Requested",
+ "community.follow.cta": "Follow",
+ "community.stat.posts": "Posts",
+ "community.stat.articles": "Articles",
+ "community.stat.followers": "Followers",
+ "community.stat.following": "Following",
+ "community.stat.listings": "Listings",
+ "community.feed.title": "Community feed",
+ "community.title": "Community",
+ "community.authors": "Community authors",
+ "community.tab.posts": "Posts ({count})",
+ "community.tab.shop": "Shop ({count})",
+ "community.tab.articles": "Articles ({count})",
+ "community.tab.followers": "Followers ({count})",
+ "community.tab.following": "Following ({count})",
+ "community.profile.private": "This profile is private",
+ "community.feed.following_title": "Posts from followed profiles",
+ "community.feed.empty_following": "No posts from followed profiles yet.",
+ "community.shop.title": "Your shop",
+ "community.shop.manage": "Manage your listings",
+ "community.remove": "Remove",
+ "community.shop.empty": "No listings yet.",
+ "community.articles.title": "Your articles",
+ "community.articles.manage": "Manage your articles",
+ "community.articles.empty": "No articles yet.",
+ "community.posts.empty": "No posts yet.",
+ "community.media.read_error": "The selected media could not be read.",
+ "community.translation.search_error": "Translation search failed.",
+ "community.post.updated": "Post updated",
+ "community.post.published": "Post published",
+ "community.changes_live": "Your changes are now live.",
+ "community.post.live": "Your community post is now live.",
+ "community.modal.close": "Close",
+ "community.post.edit_title": "Edit community post",
+ "community.post.create_title": "Share a new post",
+ "community.media.add_more": "Add more media",
+ "community.media.choose": "Choose images or MP4 videos",
+ "community.field.title": "Title",
+ "community.post.title_placeholder": "Give your post a clear title",
+ "community.field.description": "Description",
+ "community.post.description_placeholder": "Add context, details, or a story",
+ "community.field.tags": "Tags",
+ "community.saving": "Saving…",
+ "community.publishing": "Publishing…",
+ "community.save_changes": "Save changes",
+ "community.post.submit": "Post",
+ "community.article.updated": "Article updated",
+ "community.article.published": "Article published",
+ "community.article.live": "It now appears in the site journal.",
+ "community.article.delete_confirm": "Permanently delete this article? This action cannot be undone.",
+ "community.article.deleted": "Article deleted",
+ "community.article.deleted_body": "The article has been removed.",
+ "community.article.delete_error": "The article could not be deleted.",
+ "community.article.edit_title": "Edit article",
+ "community.article.create_title": "Write a new article",
+ "community.article.edit_description": "Update the details below — changes publish immediately to the site journal.",
+ "community.article.create_description": "Collaborator accounts can publish this article directly to the site journal.",
+ "community.field.slug": "Slug",
+ "community.field.excerpt": "Excerpt",
+ "community.field.body": "Body",
+ "community.publish": "Publish",
+ "community.deleting": "Deleting…",
+ "community.delete": "Delete",
+ "community.product.image_read_error": "An image could not be read.",
+ "community.product.file_read_error": "A file could not be read.",
+ "community.product.updated": "Product updated",
+ "community.product.published": "Product listed",
+ "community.product.live": "It now appears in your shop and the community marketplace.",
+ "community.product.edit_title": "Edit product",
+ "community.product.create_title": "List a new product",
+ "community.product.edit_description": "Update the details below — changes publish immediately to your marketplace shop.",
+ "community.product.create_description": "Collaborator accounts can publish this product directly to their marketplace shop.",
+ "community.product.media.add_more": "Add more product images",
+ "community.product.media.choose": "Choose product images",
+ "community.product.field.name": "Product name",
+ "community.product.field.brand": "Brand",
+ "community.product.field.subtitle": "Subtitle",
+ "community.product.field.type": "Product type",
+ "community.product.field.external_url": "External product URL",
+ "community.product.field.button_text": "Button text",
+ "community.product.field.sku": "SKU",
+ "community.product.field.stock": "Stock quantity",
+ "community.product.field.category": "Category",
+ "community.product.submit": "List product"
+}
diff --git a/assets/storefront-ui-strings/ja.json b/assets/storefront-ui-strings/ja.json
new file mode 100644
index 0000000..80ea73d
--- /dev/null
+++ b/assets/storefront-ui-strings/ja.json
@@ -0,0 +1,677 @@
+{
+ "cart.title": "カート",
+ "cart.aria": "ショッピングカート",
+ "cart.close": "カートを閉じる",
+ "cart.empty.heading": "カートは空です",
+ "cart.empty.body": "まだ商品が追加されていません。",
+ "cart.empty.body_alt": "ショップから商品を追加すると、ここに表示されます。",
+ "cart.you_might_like": "こちらもおすすめ",
+ "cart.subtotal": "小計",
+ "cart.shipping_notice": "送料と税金は購入手続き時に計算されます。",
+ "cart.view_cart": "カートを見る",
+ "cart.checkout": "購入手続きへ",
+ "cart.continue_shopping": "買い物を続ける",
+ "cart.add": "カートに追加",
+ "cart.added": "追加済み ✓",
+ "cart.remove": "削除",
+ "cart.ready": "購入手続きに進みますか?",
+ "cart.item_singular": "点",
+ "cart.item_plural": "点",
+ "cart.continue_checkout": "購入手続きに進む",
+ "cart.shipping": "送料",
+ "cart.tax": "税金",
+ "cart.digital_delivery": "デジタル配信",
+ "cart.free": "無料",
+ "cart.shipping_destination": "配送先のプレビュー",
+ "cart.shipping_destination_aria": "配送先をプレビュー",
+ "cart.empty.cart_title": "カートは空です",
+ "checkout.title": "購入手続き",
+ "checkout.progress_aria": "購入手続きの進行状況",
+ "checkout.step.cart": "カート",
+ "checkout.step.checkout": "購入手続き",
+ "checkout.step.confirmation": "確認",
+ "checkout.billing.title": "請求先情報",
+ "checkout.field.first_name": "名",
+ "checkout.field.last_name": "姓",
+ "checkout.field.company": "会社名",
+ "checkout.field.country": "国/地域",
+ "checkout.field.address1": "住所",
+ "checkout.field.address1_helper": "番地・町名",
+ "checkout.field.address2": "建物名・部屋番号など",
+ "checkout.field.optional": "任意",
+ "checkout.field.city": "市区町村",
+ "checkout.field.state": "都道府県",
+ "checkout.field.postcode": "郵便番号",
+ "checkout.field.phone": "電話番号",
+ "checkout.field.email": "メールアドレス",
+ "checkout.field.phone_error": "有効な電話番号を入力してください",
+ "checkout.field.email_error": "有効なメールアドレスを入力してください",
+ "checkout.field.country_placeholder": "国を選択…",
+ "checkout.digital_notice": "デジタル配信です。お支払い後、ウェブサイトからすぐに受け取れます。配送先住所や配送方法の指定は不要です。",
+ "checkout.account.title": "アカウント",
+ "checkout.account.create": "アカウントを作成しますか?",
+ "checkout.field.username": "ユーザー名",
+ "checkout.field.password": "パスワード",
+ "checkout.marketing_consent": "新商品、キャンペーン、再入荷のお知らせをメールで受け取る。",
+ "checkout.guest_notice": "購入手続き中に、メールアドレスを使用してアカウントが作成されます。",
+ "checkout.delivery.title": "配送",
+ "checkout.ship_to_different": "別の住所に配送しますか?",
+ "checkout.shipping_method": "配送方法",
+ "checkout.shipping_loading": "利用可能な配送方法を読み込み中…",
+ "checkout.subtotal": "小計",
+ "checkout.discount": "割引",
+ "checkout.shipping": "送料",
+ "checkout.tax": "税金",
+ "checkout.free": "無料",
+ "checkout.free_shipping_nudge": "で通常配送が無料になります — カートに戻る",
+ "checkout.back_to_cart": "カートに戻る",
+ "checkout.order_notes.title": "注文メモ",
+ "checkout.order_notes.label": "ご注文に関するメモ",
+ "checkout.order_notes.helper": "任意 — 例:配送時のご要望",
+ "checkout.coupon.title": "クーポンをお持ちですか?",
+ "checkout.coupon.label": "クーポンコード",
+ "checkout.coupon.apply": "コードを適用",
+ "checkout.coupon.applying": "適用中…",
+ "checkout.coupon.applied": "クーポン「{code}」を適用しました",
+ "checkout.coupon.remove": "削除",
+ "checkout.coupon.error": "クーポンを適用できませんでした",
+ "checkout.payment.title": "お支払い方法",
+ "checkout.payment.online": "オンライン決済",
+ "checkout.payment.online_desc": "カードおよびStripeがサポートするその他のオンライン決済方法。",
+ "checkout.payment.blik": "BLIK",
+ "checkout.payment.blik_desc": "Stripeを通じて6桁のBLIKコードを使用し、PLNですぐに支払います。",
+ "checkout.payment.blik_label": "BLIKコード",
+ "checkout.payment.blik_error": "BLIKコードは6桁で入力してください",
+ "checkout.payment.blik_expired": "コードの有効期限が切れました。新しい認証時間を取得するには、もう一度注文してください。",
+ "checkout.payment.bacs": "銀行振込",
+ "checkout.payment.bacs_desc": "注文後、ショップの振込案内に従って銀行からお支払いください。",
+ "checkout.payment.instructions": "お支払い方法のご案内",
+ "checkout.payment.cod": "代金引換",
+ "checkout.payment.cod_desc": "商品到着時に現金でお支払いください。",
+ "checkout.payment.cheque": "銀行小切手",
+ "checkout.payment.cheque_desc": "小切手を郵送してください。決済完了後に商品を発送します。",
+ "checkout.payment.unavailable_digital": "デジタル注文では利用できません",
+ "checkout.payment.unavailable": "現在利用できません",
+ "checkout.payment.ssl": "取引はSSL暗号化で保護されています",
+ "checkout.payment.unavailable_backend": "このバックエンドでは現在、暗号資産決済を利用できません。",
+ "checkout.payment.method_unavailable": "選択したお支払い方法は現在利用できません。",
+ "checkout.summary.title": "注文内容の確認",
+ "checkout.cta": "注文を確定する",
+ "checkout.terms": "ウェブサイトの利用規約を読み、同意します",
+ "checkout.privacy": "プライバシーポリシーに記載された個人データの取り扱いに同意します",
+ "checkout.terms_link": "利用規約",
+ "checkout.privacy_link": "プライバシーポリシー",
+ "checkout.error.billing_required": "注文を確定する前に、必須の請求先情報を入力してください。",
+ "checkout.error.method_unavailable": "選択したお支払い方法は利用できません。",
+ "order_success.breadcrumb": "注文確定",
+ "order_success.section": "注文確認",
+ "order_success.section_digital": "デジタル注文の確認",
+ "order_success.heading": "注文が完了しました",
+ "order_success.thank_you": "{name}、ありがとうございます。ご注文 #{number} が確定しました。領収書をメールでお送りしました。",
+ "order_success.thank_you_digital": "{name}、ありがとうございます。デジタル注文 #{number} が確定しました。以下からダウンロードできます。領収書もメールでお送りしました。",
+ "order_success.customer_fallback": "お客様",
+ "order_success.empty.heading": "完了した注文が見つかりません",
+ "order_success.empty.message": "このページには、このブラウザセッションで最後に完了した注文が表示されます。",
+ "order_success.refreshing": "注文情報を更新中",
+ "order_success.refresh_error": "最新情報を取得できませんでした。購入手続き時に保存された注文内容を表示しています。",
+ "order_success.order_label": "注文 #{number}",
+ "order_success.quantity": "数量:{quantity}",
+ "order_success.shipping_to": "配送先",
+ "order_success.delivery": "配送",
+ "order_success.details.order": "注文詳細",
+ "order_success.details.delivery_payment": "配送とお支払い",
+ "order_success.receipt": "領収書",
+ "order_success.currency": "通貨",
+ "order_success.account_login_error": "注文は作成されましたが、アカウントの自動設定が完了しませんでした:{error} ログインして「注文履歴」を確認し、注文が見つからない場合はサポートにお問い合わせください。",
+ "order_success.delivery_note": "ご注文はショップの処理フローに入りました。",
+ "order_success.native_note": "最新のお支払い状況と注文ステータスは、標準の注文ページでご確認ください。",
+ "order_success.payment.cod": "代金引換",
+ "order_success.payment.cheque": "小切手払い",
+ "order_success.payment.bacs": "銀行振込",
+ "order_success.payment.crypto": "暗号資産決済",
+ "order_success.payment.blik": "BLIK",
+ "order_success.payment.card": "カード決済",
+ "order_success.row.subtotal": "小計",
+ "order_success.row.discount": "割引",
+ "order_success.row.discount_with_codes": "割引({codes})",
+ "order_success.row.shipping": "送料",
+ "order_success.row.tax": "税金",
+ "order_success.row.free": "無料",
+ "order_success.row.digital_delivery": "デジタル配信",
+ "order_success.row.total": "お支払い合計",
+ "order_success.cta.shopping": "買い物を続ける",
+ "order_success.cta.native": "標準の注文ページを開く",
+ "order_success.cta.track": "注文を追跡",
+ "order_success.cta.orders": "注文履歴を見る",
+ "order_success.cta.private": "非公開の注文ページを開く",
+ "order_success.cta.pdf": "領収書をPDFで保存",
+ "order_success.cta.pdf_hint": "ブラウザーの印刷画面を開き、この領収書をPDFとして保存できます。",
+ "order_success.cta.help": "注文について問い合わせる",
+ "order_success.promo": "いつもご利用いただきありがとうございます。次回のご注文が15%オフになります。",
+ "order_success.support": "ご注文についてご不明な点がありますか?",
+ "order_success.contact": "お問い合わせ",
+ "order_success.downloads": "ダウンロード",
+ "order_success.downloads_note": "ファイルはアカウントの「ダウンロード」タブからいつでも入手できます。このページを保存する必要はありません。",
+ "order_success.download.available": "利用可能",
+ "order_success.download.unavailable": "利用不可",
+ "order_success.download.expires": "有効期限:",
+ "order_success.download.remaining": "残りダウンロード回数:",
+ "order_success.download.unlimited": "無制限",
+ "order_success.download.cta": "ダウンロード",
+ "order_success.support.trouble": "ダウンロードできない場合",
+ "order_status.pending": "入金待ち",
+ "order_status.processing": "処理中",
+ "order_status.on-hold": "保留中",
+ "order_status.completed": "完了",
+ "order_status.cancelled": "キャンセル済み",
+ "order_status.failed": "失敗",
+ "order_status.refunded": "返金済み",
+ "order_details.private_title": "非公開の注文",
+ "order_details.heading": "注文 #{number}",
+ "order_details.guest_expires": "このゲスト用非公開リンクの有効期限は{date}です。",
+ "order_details.loading": "注文を読み込み中",
+ "order_details.unavailable": "注文が見つからないか、利用できない、または24時間有効のゲストリンクが期限切れです。",
+ "order_details.back_account": "アカウントに戻る",
+ "auth.title.login": "おかえりなさい",
+ "auth.title.register": "アカウントを作成",
+ "auth.title.forgot": "パスワードをお忘れですか?",
+ "auth.breadcrumb.login": "ログイン",
+ "auth.breadcrumb.register": "新規登録",
+ "auth.breadcrumb.forgot": "パスワードを忘れた場合",
+ "auth.desc.login": "ログインすると、注文の追跡、ウィッシュリストの管理、スムーズな購入手続きができます。",
+ "auth.desc.register": "Superfunkyに登録して、あなた向けのおすすめやスムーズな購入手続きをご利用ください。",
+ "auth.desc.forgot": "アカウントに再度アクセスするための安全なリンクをメールでお送りします。",
+ "auth.tab.login": "ログイン",
+ "auth.tab.register": "新規登録",
+ "auth.tab.forgot": "パスワードを忘れた場合",
+ "auth.password_updated": "パスワードを更新しました。新しいパスワードでログインしてください。",
+ "auth.or_continue": "または次の方法で続ける",
+ "auth.continue_with": "{provider}で続ける",
+ "auth.brand_tagline": "サイトアカウントと連携した、モダンなストア体験。",
+ "auth.login.username": "ユーザー名またはメールアドレス",
+ "auth.login.password": "パスワード",
+ "auth.login.remember": "ログイン状態を保持する",
+ "auth.login.forgot_link": "パスワードをお忘れですか?",
+ "auth.login.error": "ログインできませんでした。",
+ "auth.login.cta": "ログイン",
+ "auth.login.cta_loading": "ログイン中…",
+ "auth.register.first_name": "名",
+ "auth.register.last_name": "姓",
+ "auth.register.username": "ユーザー名",
+ "auth.register.username_placeholder": "例:funk_rider.99",
+ "auth.register.username_helper": "コミュニティプロフィールのURLに使用されます — 英字、数字、アンダースコア、ハイフン、ドット",
+ "auth.register.email": "メールアドレス",
+ "auth.register.password": "パスワード",
+ "auth.register.password_helper": "8文字以上",
+ "auth.register.confirm": "パスワード(確認)",
+ "auth.register.newsletter": "新商品、キャンペーン、再入荷に関するメールを不定期で受け取る。いつでも配信を停止できます。",
+ "auth.register.error": "アカウントを作成できませんでした。",
+ "auth.register.cta": "アカウントを作成",
+ "auth.register.cta_loading": "アカウントを作成中…",
+ "auth.register.success.heading": "アカウントを作成しました。",
+ "auth.register.success.body": "メールアドレスとパスワードでログインできます。",
+ "auth.register.success.cta": "ログインへ進む",
+ "auth.forgot.field": "ユーザー名またはメールアドレス",
+ "auth.forgot.error": "リセット用メールを送信できませんでした。",
+ "auth.forgot.cta": "リセット用リンクを送信",
+ "auth.forgot.cta_loading": "送信中…",
+ "auth.forgot.success": "{identity}のアカウントが存在する場合は、リセット用リンクを送信しました。",
+ "auth.reset.breadcrumb": "パスワードをリセット",
+ "auth.reset.error.mismatch": "パスワードが一致しません。",
+ "auth.reset.error.failed": "パスワードをリセットできませんでした。",
+ "auth.oauth.error": "プロバイダー経由でログインできませんでした。",
+ "auth.oauth.callback_error": "プロバイダーからのコールバックが不完全か、無効です。",
+ "validation.passwords_mismatch": "パスワードが一致しません",
+ "validation.password.min_length": "8文字以上で入力してください。",
+ "validation.password.uppercase": "大文字を1文字以上含めてください。",
+ "validation.password.lowercase": "小文字を1文字以上含めてください。",
+ "validation.password.number": "数字を1文字以上含めてください。",
+ "validation.password.special": "特殊文字を1文字以上含めてください。",
+ "validation.required": "{label}は必須です。",
+ "validation.min_length": "{label}は{min}文字以上で入力してください。",
+ "validation.max_length": "{label}は{max}文字以内で入力してください。",
+ "validation.email": "{label}に有効なメールアドレスを入力してください。",
+ "validation.phone": "{label}に有効な電話番号を入力してください。",
+ "validation.username_chars": "{label}に使用できるのは、英字、数字、アンダースコア、ハイフン、ドットのみです。",
+ "account.tab.dashboard": "ダッシュボード",
+ "account.tab.orders": "注文履歴",
+ "account.tab.addresses": "住所",
+ "account.tab.community": "コミュニティ",
+ "account.logout": "ログアウト",
+ "account.signin_register": "ログインまたは新規登録",
+ "account.guest.name": "ゲストアカウント",
+ "account.guest.email_placeholder": "ログインしてアカウントを読み込む",
+ "account.loading": "アカウントを読み込み中",
+ "account.empty_state": "ログインすると、プロフィールとアカウント概要を表示できます。",
+ "account.greeting": "こんにちは、{name}さん 👋",
+ "account.role": "ロール:{role}",
+ "account.verified": "認証済みアカウント",
+ "account.orders.empty": "注文履歴はまだありません。",
+ "account.orders.loading": "注文履歴を読み込み中…",
+ "account.orders.sign_in": "注文履歴を表示するにはログインしてください。",
+ "account.orders.view_details": "詳細を見る",
+ "account.addresses.loading": "保存済みの住所を読み込み中…",
+ "account.addresses.sign_in": "請求先と配送先の住所を管理するにはログインしてください。",
+ "account.addresses.save": "住所を保存",
+ "account.addresses.save_error": "住所を保存できませんでした。",
+ "account.guest.benefit1_eyebrow": "あなた専用のストア",
+ "account.guest.benefit1_title": "アカウント体験をひとつに",
+ "account.guest.benefit2_eyebrow": "非公開の注文履歴",
+ "account.guest.benefit2_title": "すべての購入を一か所で追跡",
+ "account.guest.benefit3_eyebrow": "スムーズな購入手続き",
+ "account.guest.benefit3_title": "請求先と配送先情報を保存",
+ "account.guest.benefit4_eyebrow": "コミュニティとマーケットプレイス",
+ "account.guest.benefit4_title": "ロールに割り当てられたツールを利用",
+ "account.guest.cta.login": "ログイン",
+ "account.guest.cta.register": "アカウントを作成",
+ "product.add_to_cart": "カートに追加",
+ "product.added": "追加済み ✓",
+ "product.buy_now": "今すぐ購入",
+ "product.choose_options": "利用可能なオプションを選ぶ",
+ "product.select_options": "オプションを選択",
+ "product.grouped": "グループ商品",
+ "product.add_wishlist": "ウィッシュリストに追加",
+ "product.remove_wishlist": "ウィッシュリストから削除",
+ "product.save_reading": "リーディングリストに保存",
+ "product.remove_reading": "リーディングリストから削除",
+ "product.unavailable": "商品を利用できません",
+ "product.loading": "商品を読み込み中",
+ "product.related_loading": "関連商品を読み込み中",
+ "search.placeholder": "商品、ストーリー、ユーザー、タグを検索…",
+ "search.open": "検索を開く",
+ "search.close": "検索を閉じる",
+ "header.theme.toggle": "カラーモードを切り替える",
+ "header.theme.light": "ライトモードに切り替える",
+ "header.theme.dark": "ダークモードに切り替える",
+ "header.push.enable": "プッシュ通知を有効にする",
+ "header.push.disable": "プッシュ通知を無効にする",
+ "header.push.enabled": "プッシュ通知は有効です",
+ "header.account": "アカウント",
+ "header.reading_list": "リーディングリスト",
+ "header.wishlist": "ウィッシュリスト",
+ "header.cart": "カート",
+ "header.sync_error": "{label}(同期エラー)",
+ "header.sync_error_detail": "{label} — {message}",
+ "header.menu.open": "メニューを開く",
+ "header.menu.site": "サイトメニュー",
+ "header.menu.title": "メニュー",
+ "header.menu.close": "メニューを閉じる",
+ "header.navigation.main": "メインナビゲーション",
+ "header.navigation.mobile": "モバイルナビゲーション",
+ "search.aria": "検索結果",
+ "search.results": "検索結果",
+ "search.loading": "サイトを検索中…",
+ "search.no_results": "「{query}」の検索結果はありません",
+ "search.unavailable": "検索を利用できません",
+ "search.type.product": "商品",
+ "search.type.post": "投稿",
+ "search.type.page": "ページ",
+ "search.type.post_category": "投稿カテゴリー",
+ "search.type.post_tag": "投稿タグ",
+ "search.type.product_category": "商品カテゴリー",
+ "search.type.product_tag": "商品タグ",
+ "search.type.product_brand": "商品ブランド",
+ "search.type.author": "投稿者",
+ "search.type.community_post": "コミュニティ投稿",
+ "search.type.community_author": "コミュニティメンバー",
+ "search.type.community_tag": "コミュニティタグ",
+ "search.group.catalog": "カタログ",
+ "search.group.editorial": "記事",
+ "search.group.community": "コミュニティ",
+ "search.group.pages": "ページ",
+ "nav.home": "ホーム",
+ "nav.main_aria": "メインナビゲーション",
+ "nav.mobile_aria": "モバイルナビゲーション",
+ "nav.site_aria": "サイトメニュー",
+ "nav.open_menu": "メニューを開く",
+ "nav.close_menu": "メニューを閉じる",
+ "nav.account": "アカウント",
+ "nav.cart": "カート",
+ "nav.wishlist": "ウィッシュリスト",
+ "nav.reading_list": "リーディングリスト",
+ "nav.toggle_dark": "ダークモードを切り替える",
+ "nav.select_currency": "通貨を選択",
+ "nav.select_language": "言語を選択",
+ "nav.close_newsletter": "ニュースレター登録を閉じる",
+ "nav.close_quickview": "クイックビューを閉じる",
+ "nav.breadcrumb": "パンくずリスト",
+ "image.close": "画像ビューアーを閉じる",
+ "image.next": "次の画像",
+ "image.prev": "前の画像",
+ "image.viewer_aria": "画像ビューアー",
+ "cookie.title": "Cookieの同意",
+ "cookie.settings": "Cookie設定",
+ "cookie.manage": "Cookieの設定を管理",
+ "wishlist.loading": "保存済みの商品を読み込み中",
+ "wishlist.empty": "商品はまだありません。",
+ "reading_list.loading": "保存済みの記事を読み込み中",
+ "reading_list.empty": "記事はまだありません。",
+ "community.share": "新しい投稿をシェア",
+ "community.write": "新しい記事を書く",
+ "community.list_product": "新しい商品を出品",
+ "community.profile.sections": "プロフィールセクション",
+ "community.copy_link": "リンクをコピー",
+ "loading.page": "ページを読み込み中",
+ "loading.content": "コンテンツを読み込み中",
+ "loading.post": "投稿を読み込み中",
+ "loading.product": "商品を読み込み中",
+ "loading.community_post": "コミュニティ投稿を読み込み中",
+ "loading.community_feed": "コミュニティフィードを読み込み中",
+ "loading.community_profile": "コミュニティプロフィールを読み込み中",
+ "error.page_unavailable": "ページを利用できません",
+ "error.post_unavailable": "投稿を利用できません",
+ "error.product_unavailable": "商品を利用できません",
+ "error.community_post_unavailable": "コミュニティ投稿を利用できません",
+ "error.author_unavailable": "投稿者情報を利用できません",
+ "error.archive_unavailable": "アーカイブを利用できません",
+ "error.content_unavailable": "コンテンツを利用できません",
+ "error.like_failed": "「いいね」を更新できませんでした。",
+ "error.review_failed": "レビューを送信できませんでした。",
+ "error.reply_failed": "返信を送信できませんでした。",
+ "error.article_failed": "記事を公開できませんでした。",
+ "error.post_failed": "投稿を公開できませんでした。",
+ "error.product_listing_failed": "商品を出品できませんでした。",
+ "error.account_load": "アカウントを読み込めませんでした",
+ "notification.dismiss": "通知を閉じる",
+ "notification.push.enable_error": "プッシュ通知を有効にできませんでした。",
+ "notification.push.disable_error": "プッシュ通知を無効にできませんでした。",
+ "newsletter.join": "メーリングリストに登録",
+ "newsletter.email": "メールアドレス",
+ "newsletter.email_placeholder": "you@example.com",
+ "newsletter.subscribe": "登録",
+ "newsletter.subscribing": "登録中…",
+ "newsletter.subscribed": "ありがとうございます。購読登録が完了しました。",
+ "newsletter.signup_error": "ニュースレターに登録できませんでした。",
+ "newsletter.unsubscribe": "登録解除",
+ "newsletter.consent": "続行するには、プライバシーに関する注意事項に同意してください。",
+ "newsletter.email_invalid": "有効なメールアドレスを入力してください。",
+ "footer.newsletter.title": "新商品やオファーをいち早くお届け",
+ "footer.newsletter.description": "厳選アイテム、発売情報、毎月の最新情報をお届けします。迷惑メールは送信せず、いつでも解除できます。",
+ "footer.newsletter.privacy": "マーケティングメールの受信とプライバシーポリシーに同意します。",
+ "footer.assistant.title": "AIショッピングアシスタント — サイズ、注文、おすすめについてご相談ください",
+ "footer.assistant.tab": "AIショッピングアシスタント",
+ "footer.spotify.tab": "Spotifyプレーヤー",
+ "footer.assistant_spotify.aria": "アシスタント / Spotify",
+ "footer.radio.title": "Superfunky Radio",
+ "footer.radio.description": "ブラウジングに合うインストゥルメンタル・ジャズホップ。お好きな曲、アルバム、ポッドキャストに変更できます。",
+ "footer.nav.expand": "{label}を展開",
+ "footer.nav.collapse": "{label}を折りたたむ",
+ "cookie.banner.accept_all": "すべて同意する",
+ "cookie.banner.description": "{providerName}は、ウェブサイトを正しく機能させるため、また分析や広告の目的でCookieを使用しています。詳しくは",
+ "cookie.banner.policy_link": "Cookieポリシーをご覧ください",
+ "cookie.category.functional": "機能性",
+ "cookie.category.functional_desc": "常にオン",
+ "cookie.category.functional_title": "カート、ウィッシュリスト、この選択の記憶など、主要な機能に必要です。",
+ "cookie.category.marketing": "マーケティング",
+ "cookie.category.marketing_desc": "広告",
+ "cookie.category.tracking": "トラッキング",
+ "cookie.category.tracking_desc": "アクセス解析",
+ "cookie.category.performance": "パフォーマンス",
+ "cookie.category.performance_desc": "レイアウト",
+ "cookie.item.delete": "削除",
+ "cookie.item.delete_aria": "{name}を削除",
+ "cookie.item.expires_in": "{lifetime}後に期限切れ",
+ "cookie.item.required": "必須",
+ "cookie.manager.accept": "同意する",
+ "cookie.manager.close": "閉じる",
+ "cookie.manager.decline": "拒否する",
+ "cookie.manager.nothing_to_show": "表示するものはこれ以上ありません。",
+ "cookie.manager.save": "保存",
+ "cookie.manager.subtitle": "使用を許可する任意のCookieを選択してください。",
+ "cookie.manager.tab_cookies": "Cookie一覧",
+ "cookie.manager.tab_preferences": "設定",
+ "filters.all_authors": "すべての著者",
+ "filters.all_brands": "すべてのブランド",
+ "filters.all_categories": "すべてのカテゴリー",
+ "filters.all_tags": "すべてのタグ",
+ "filters.aria_label": "{title}のフィルター",
+ "filters.author_aria": "著者で絞り込む",
+ "filters.brand_aria": "ブランドで絞り込む",
+ "filters.browse": "閲覧する",
+ "filters.category_aria": "カテゴリーで絞り込む",
+ "filters.clear": "フィルターをクリア",
+ "filters.default_empty_social": "まだ投稿はありません。最初の投稿をしてみましょう。",
+ "filters.default_title_posts": "最新の投稿",
+ "filters.default_title_products": "商品",
+ "filters.default_title_social": "コミュニティフィード",
+ "filters.end_reached": "最後まで到達しました。",
+ "filters.interested_in": "興味のあるカテゴリー",
+ "filters.layout_compact": "コンパクトグリッド(プロフィール風)",
+ "filters.layout_grid3": "3列",
+ "filters.layout_grid4": "4列",
+ "filters.layout_label": "レイアウト",
+ "filters.layout_list": "リスト",
+ "filters.layout_masonry": "マソンリー",
+ "filters.load_mode_infinite": "無限スクロール",
+ "filters.load_mode_pages": "ページ",
+ "filters.load_more": "もっと見る",
+ "filters.loading_more": "読み込み中…",
+ "filters.next": "次へ →",
+ "filters.no_posts_match": "これらのフィルターに一致する投稿はありません。",
+ "filters.no_products_match": "これらのフィルターに一致する商品はありません。",
+ "filters.pagination_aria": "{title}のページ送り",
+ "filters.prev": "← 前へ",
+ "filters.rating_3": "★3以上",
+ "filters.rating_4": "★4以上",
+ "filters.rating_45": "★4.5以上",
+ "filters.rating_any": "評価を問わない",
+ "filters.rating_aria": "最低評価で絞り込む",
+ "filters.search_feed": "フィードを検索",
+ "filters.search_posts": "投稿を検索",
+ "filters.search_products": "商品を検索",
+ "filters.showing_label": "表示中",
+ "filters.showing_suffix": "/ {total}件",
+ "filters.sort_default_order": "デフォルトの並び順",
+ "filters.sort_featured": "おすすめ",
+ "filters.sort_feed_aria": "フィードを並べ替え",
+ "filters.sort_name": "名前",
+ "filters.sort_newest": "新着順",
+ "filters.sort_oldest": "古い順",
+ "filters.sort_popular": "人気順",
+ "filters.sort_posts_aria": "投稿を並べ替え",
+ "filters.sort_price_asc": "価格が安い順",
+ "filters.sort_price_desc": "価格が高い順",
+ "filters.sort_products_aria": "商品を並べ替え",
+ "filters.sort_rating": "評価が高い順",
+ "filters.sort_title": "タイトル",
+ "filters.tag_aria": "タグで絞り込む",
+ "image.show_label": "{label}を表示",
+ "newsletter.default_body": "インサイダーリストに登録すると、Superfunkyの世界からの先行アクセス、限定オファー、厳選ストーリーをお届けします。",
+ "newsletter.default_title": "次のお気に入りが登場したら、いち早くお知らせします。",
+ "newsletter.eyebrow": "最新情報をチェック",
+ "newsletter.image_placeholder_body": "ここにフルブリードの商品画像や特集画像を配置します。",
+ "newsletter.image_placeholder_title": "画像プレースホルダー",
+ "newsletter.maybe_later": "また今度",
+ "newsletter.subscribed_body": "ご登録ありがとうございます。次回の新作の先行情報や限定アップデートを近日中にお届けします。",
+ "newsletter.trust.easy_unsubscribe": "簡単に配信停止",
+ "newsletter.trust.no_spam": "スパムなし",
+ "newsletter.trust.privacy": "プライバシーを尊重",
+ "order_success.download.downloading": "ダウンロード中…",
+ "order_success.download.empty": "この注文にはダウンロード可能なファイルが含まれていません。",
+ "order_success.download.error": "ファイルのダウンロードに失敗しました。もう一度お試しいただくか、サポートまでお問い合わせください。",
+ "order_success.download.loading": "安全なダウンロードリンクを読み込み中…",
+ "order_success.download.never": "なし",
+ "product.cta.gallery_thumbnails_aria": "{name}のギャラリーサムネイル",
+ "product.cta.learn_more": "詳しく見る",
+ "product.cta.quick_view": "クイックビュー",
+ "product.cta.quick_view_aria": "クイックビュー — {name}",
+ "product.cta.show_photo_aria": "写真{index}を表示",
+ "product.cta.view_product": "商品を見る",
+ "product.cta.view_product_aria": "{name}を見る",
+ "product.cta.view_products": "商品一覧を見る",
+ "product.image_alt": "商品画像",
+ "product.image_alt_indexed": "商品画像{index}",
+ "product.status.available": "在庫あり",
+ "product.status.new": "新着",
+ "product.status.promotion": "セール",
+ "product.status.promotion_percent": "{percent}%オフ",
+ "product.status.sold_out": "売り切れ",
+ "product.variation_unavailable.description": "在庫のある組み合わせを選択してください。",
+ "product.variation_unavailable.title": "このバリエーションは選択できません",
+ "reading_list.browse_blog": "ブログを見る",
+ "reading_list.cap_error": "リーディングリストの上限を読み込めませんでした: {error}",
+ "reading_list.cap_suffix": "/ {cap}",
+ "reading_list.category_fallback": "ジャーナル",
+ "reading_list.count": "保存済みの{item}: {count}件",
+ "reading_list.empty_hint": "ブログの記事にあるブックマークボタンを使うと、後で読むために保存できます。",
+ "reading_list.item_plural": "記事",
+ "reading_list.item_singular": "記事",
+ "reading_list.load_error": "保存済みの記事を読み込めませんでした: {message}",
+ "reading_list.mark_read": "既読にする",
+ "reading_list.read": "既読",
+ "reading_list.reading_time": "読了目安 {minutes}分",
+ "reading_list.sign_in_suffix": "して端末間でリーディングリストを同期しましょう。",
+ "reading_list.sync_error": "リーディングリストを同期できませんでした: {error}",
+ "reading_list.sync_local": "このブラウザにローカル保存されています",
+ "reading_list.sync_synced": "アカウントと同期済み",
+ "reading_list.unread_title": "未読",
+ "share.label": "シェア",
+ "share.link_copied": "リンクをコピーしました!",
+ "share.on.facebook": "Facebookでシェア",
+ "share.on.linkedin": "LinkedInでシェア",
+ "share.on.telegram": "Telegramでシェア",
+ "share.on.tiktok": "TikTokでシェア",
+ "share.on.whatsapp": "WhatsAppでシェア",
+ "share.on.x": "Xでシェア",
+ "share.via_email": "メールでシェア",
+ "wishlist.browse_shop": "ショップを見る",
+ "wishlist.cap_error": "ウィッシュリストの上限を読み込めませんでした: {error}",
+ "wishlist.cap_suffix": "/ {cap}",
+ "wishlist.clear_unavailable": "利用できない商品を削除",
+ "wishlist.count": "保存済みの{item}: {count}件",
+ "wishlist.empty_hint": "商品カードのハートアイコンをタップすると、後で見返せるようにここに保存されます。次回の訪問時も保存されたままです。",
+ "wishlist.item_plural": "商品",
+ "wishlist.item_singular": "商品",
+ "wishlist.load_error": "保存済みの商品を読み込めませんでした: {message}",
+ "wishlist.sign_in_suffix": "して端末間でウィッシュリストを同期しましょう。",
+ "wishlist.sync_error": "ウィッシュリストを同期できませんでした: {error}",
+ "wishlist.sync_local": "このブラウザにローカル保存されています",
+ "wishlist.sync_synced": "アカウントと同期済み",
+ "wishlist.unavailable_message": "保存済みのこれらの商品は、現在のカタログには存在しません。",
+ "account.tab.downloads": "ダウンロード",
+ "account.avatar.saving": "アバターを保存中…",
+ "account.avatar.change": "アバターを変更",
+ "account.avatar.add": "アバターを追加",
+ "account.avatar.remove": "アバターを削除",
+ "account.avatar.max_size": "ファイルサイズの上限は690KBです",
+ "account.email.verification_required": "メールアドレスの確認が必要です",
+ "account.email.verification_optional": "メールアドレスの確認は任意です",
+ "account.newsletter.subscribed": "ニュースレターに登録済み",
+ "account.newsletter.not_subscribed": "ニュースレター未登録",
+ "account.stat.orders_placed": "注文件数",
+ "account.stat.saved_addresses": "保存済みの住所",
+ "account.stat.publishing_role": "公開権限",
+ "account.role.member": "メンバー",
+ "account.guest.benefit1_description": "サインインすると、認証済みプロフィールと非公開の顧客ツールを確認できます。",
+ "account.guest.benefit1_item1": "プロフィールとアカウントの状態を確認",
+ "account.guest.benefit1_item2": "注文と保存済みの配送情報をまとめて確認",
+ "account.guest.benefit1_item3": "自分の役割で利用できる公開ツールを確認",
+ "account.guest.benefit2_description": "注文履歴は非公開です。サインインすると、実際の注文状況、合計金額、商品、バリエーションの詳細を確認できます。",
+ "account.guest.benefit2_item1": "現在の発送状況を確認",
+ "account.guest.benefit2_item2": "注文明細とバリエーションを確認",
+ "account.guest.benefit2_item3": "過去の購入履歴をいつでも参照可能",
+ "account.guest.benefit3_description": "アカウントを作成すると、顧客プロフィールと購入時の住所を安全に管理できます。",
+ "account.guest.benefit3_item1": "請求先と配送先を個別に編集",
+ "account.guest.benefit3_item2": "正確な顧客情報を再利用",
+ "account.guest.benefit3_item3": "住所データをアカウント内で非公開に保持",
+ "account.guest.benefit4_description": "サインインすると公開プロフィールを管理でき、権限があればクリエイター、コラボレーター、管理者の公開機能を利用できます。",
+ "account.guest.benefit4_item1": "公開プロフィールの表示範囲を管理",
+ "account.guest.benefit4_item2": "権限があればコミュニティ投稿を公開",
+ "account.guest.benefit4_item3": "権限があれば商品の出品や記事の執筆が可能",
+ "account.guest.benefit5_eyebrow": "安全なデジタルライブラリ",
+ "account.guest.benefit5_title": "購入したファイルにいつでもアクセス",
+ "account.guest.benefit5_description": "サインインすると、アカウントで利用できるダウンロードにアクセスできます。",
+ "account.guest.benefit5_item1": "署名付きのダウンロードリンクを利用",
+ "account.guest.benefit5_item2": "有効期限と残りの回数制限を確認",
+ "account.guest.benefit5_item3": "購入情報をアカウントに紐づけて保持",
+ "community.profile.unavailable": "コミュニティプロフィールを利用できません",
+ "community.profile.private_badge": "非公開",
+ "community.role.collaborator": "コラボレーター",
+ "community.role.creator": "クリエイター",
+ "community.profile.own_badge": "あなたのプロフィールです",
+ "community.follow.following": "フォロー中",
+ "community.follow.requested": "リクエスト済み",
+ "community.follow.cta": "フォローする",
+ "community.stat.posts": "投稿",
+ "community.stat.articles": "記事",
+ "community.stat.followers": "フォロワー",
+ "community.stat.following": "フォロー中",
+ "community.stat.listings": "出品数",
+ "community.feed.title": "コミュニティフィード",
+ "community.title": "コミュニティ",
+ "community.authors": "コミュニティの著者",
+ "community.tab.posts": "投稿 ({count})",
+ "community.tab.shop": "ショップ ({count})",
+ "community.tab.articles": "記事 ({count})",
+ "community.tab.followers": "フォロワー ({count})",
+ "community.tab.following": "フォロー中 ({count})",
+ "community.profile.private": "このプロフィールは非公開です",
+ "community.feed.following_title": "フォロー中のプロフィールの投稿",
+ "community.feed.empty_following": "フォロー中のプロフィールの投稿はまだありません。",
+ "community.shop.title": "あなたのショップ",
+ "community.shop.manage": "出品を管理",
+ "community.remove": "削除",
+ "community.shop.empty": "出品はまだありません。",
+ "community.articles.title": "あなたの記事",
+ "community.articles.manage": "記事を管理",
+ "community.articles.empty": "記事はまだありません。",
+ "community.posts.empty": "投稿はまだありません。",
+ "community.media.read_error": "選択したメディアを読み込めませんでした。",
+ "community.translation.search_error": "翻訳の検索に失敗しました。",
+ "community.post.updated": "投稿を更新しました",
+ "community.post.published": "投稿を公開しました",
+ "community.changes_live": "変更内容が反映されました。",
+ "community.post.live": "コミュニティ投稿が公開されました。",
+ "community.modal.close": "閉じる",
+ "community.post.edit_title": "コミュニティ投稿を編集",
+ "community.post.create_title": "新しい投稿をシェア",
+ "community.media.add_more": "メディアを追加",
+ "community.media.choose": "画像またはMP4動画を選択",
+ "community.field.title": "タイトル",
+ "community.post.title_placeholder": "投稿にわかりやすいタイトルを付けましょう",
+ "community.field.description": "説明",
+ "community.post.description_placeholder": "背景、詳細、あるいはストーリーを追加しましょう",
+ "community.field.tags": "タグ",
+ "community.saving": "保存中…",
+ "community.publishing": "公開中…",
+ "community.save_changes": "変更を保存",
+ "community.post.submit": "投稿する",
+ "community.article.updated": "記事を更新しました",
+ "community.article.published": "記事を公開しました",
+ "community.article.live": "サイトのジャーナルに反映されました。",
+ "community.article.delete_confirm": "この記事を完全に削除しますか?この操作は取り消せません。",
+ "community.article.deleted": "記事を削除しました",
+ "community.article.deleted_body": "記事は削除されました。",
+ "community.article.delete_error": "記事を削除できませんでした。",
+ "community.article.edit_title": "記事を編集",
+ "community.article.create_title": "新しい記事を書く",
+ "community.article.edit_description": "以下の内容を更新してください。変更内容はサイトのジャーナルに即座に公開されます。",
+ "community.article.create_description": "コラボレーター権限のアカウントは、この記事をサイトのジャーナルに直接公開できます。",
+ "community.field.slug": "スラッグ",
+ "community.field.excerpt": "抜粋",
+ "community.field.body": "本文",
+ "community.publish": "公開する",
+ "community.deleting": "削除中…",
+ "community.delete": "削除",
+ "community.product.image_read_error": "画像を読み込めませんでした。",
+ "community.product.file_read_error": "ファイルを読み込めませんでした。",
+ "community.product.updated": "商品を更新しました",
+ "community.product.published": "商品を出品しました",
+ "community.product.live": "あなたのショップとコミュニティマーケットプレイスに反映されました。",
+ "community.product.edit_title": "商品を編集",
+ "community.product.create_title": "新しい商品を出品",
+ "community.product.edit_description": "以下の内容を更新してください。変更内容はマーケットプレイスのショップに即座に公開されます。",
+ "community.product.create_description": "コラボレーター権限のアカウントは、この商品をマーケットプレイスのショップに直接公開できます。",
+ "community.product.media.add_more": "商品画像を追加",
+ "community.product.media.choose": "商品画像を選択",
+ "community.product.field.name": "商品名",
+ "community.product.field.brand": "ブランド",
+ "community.product.field.subtitle": "サブタイトル",
+ "community.product.field.type": "商品タイプ",
+ "community.product.field.external_url": "外部商品URL",
+ "community.product.field.button_text": "ボタンのテキスト",
+ "community.product.field.sku": "SKU",
+ "community.product.field.stock": "在庫数",
+ "community.product.field.category": "カテゴリー",
+ "community.product.submit": "商品を出品する"
+}
diff --git a/assets/storefront-ui-strings/pl.json b/assets/storefront-ui-strings/pl.json
new file mode 100644
index 0000000..706526b
--- /dev/null
+++ b/assets/storefront-ui-strings/pl.json
@@ -0,0 +1,677 @@
+{
+ "cart.title": "Twój koszyk",
+ "cart.aria": "Koszyk zakupowy",
+ "cart.close": "Zamknij koszyk",
+ "cart.empty.heading": "Twój koszyk jest pusty",
+ "cart.empty.body": "Wygląda na to, że nic jeszcze nie dodałeś.",
+ "cart.empty.body_alt": "Dodaj produkty ze sklepu, aby je tutaj zobaczyć.",
+ "cart.you_might_like": "Może ci się spodobać",
+ "cart.subtotal": "Suma częściowa",
+ "cart.shipping_notice": "Dostawa i podatki naliczane przy realizacji zamówienia.",
+ "cart.view_cart": "Zobacz koszyk",
+ "cart.checkout": "Do kasy",
+ "cart.continue_shopping": "Kontynuuj zakupy",
+ "cart.add": "Dodaj do koszyka",
+ "cart.added": "Dodano ✓",
+ "cart.remove": "Usuń",
+ "cart.ready": "Gotowy do realizacji?",
+ "cart.item_singular": "produkt",
+ "cart.item_plural": "produkty",
+ "cart.continue_checkout": "Przejdź do kasy",
+ "cart.shipping": "Dostawa",
+ "cart.tax": "Podatek",
+ "cart.digital_delivery": "Dostawa cyfrowa",
+ "cart.free": "Gratis",
+ "cart.shipping_destination": "Podgląd miejsca dostawy",
+ "cart.shipping_destination_aria": "Podgląd miejsca dostawy",
+ "cart.empty.cart_title": "Twój koszyk jest pusty",
+ "checkout.title": "Kasa",
+ "checkout.progress_aria": "Postęp realizacji zamówienia",
+ "checkout.step.cart": "Koszyk",
+ "checkout.step.checkout": "Kasa",
+ "checkout.step.confirmation": "Potwierdzenie",
+ "checkout.billing.title": "Dane do rozliczenia",
+ "checkout.field.first_name": "Imię",
+ "checkout.field.last_name": "Nazwisko",
+ "checkout.field.company": "Nazwa firmy",
+ "checkout.field.country": "Kraj / region",
+ "checkout.field.address1": "Adres",
+ "checkout.field.address1_helper": "Numer domu i nazwa ulicy",
+ "checkout.field.address2": "Mieszkanie, lokal, piętro itp.",
+ "checkout.field.optional": "Opcjonalnie",
+ "checkout.field.city": "Miasto",
+ "checkout.field.state": "Województwo / region",
+ "checkout.field.postcode": "Kod pocztowy",
+ "checkout.field.phone": "Telefon",
+ "checkout.field.email": "Adres e-mail",
+ "checkout.field.phone_error": "Numer telefonu jest nieprawidłowy",
+ "checkout.field.email_error": "Adres e-mail jest nieprawidłowy",
+ "checkout.field.country_placeholder": "Wybierz kraj…",
+ "checkout.digital_notice": "Dostawa cyfrowa. Zamówienie jest dostępne natychmiast po płatności — nie jest wymagany adres ani metoda dostawy.",
+ "checkout.account.title": "Konto",
+ "checkout.account.create": "Założyć konto?",
+ "checkout.field.username": "Nazwa użytkownika",
+ "checkout.field.password": "Hasło",
+ "checkout.marketing_consent": "Chcę otrzymywać e-maile o nowych produktach, ofertach i wznowieniach.",
+ "checkout.guest_notice": "Konto zostanie utworzone na podany adres e-mail podczas realizacji zamówienia.",
+ "checkout.delivery.title": "Dostawa",
+ "checkout.ship_to_different": "Wysłać na inny adres?",
+ "checkout.shipping_method": "Metoda dostawy",
+ "checkout.shipping_loading": "Ładowanie metod dostawy…",
+ "checkout.subtotal": "Suma częściowa",
+ "checkout.discount": "Rabat",
+ "checkout.shipping": "Dostawa",
+ "checkout.tax": "Podatek",
+ "checkout.free": "Gratis",
+ "checkout.free_shipping_nudge": "do darmowej standardowej dostawy — wróć do koszyka",
+ "checkout.back_to_cart": "wróć do koszyka",
+ "checkout.order_notes.title": "Uwagi do zamówienia",
+ "checkout.order_notes.label": "Uwagi do twojego zamówienia",
+ "checkout.order_notes.helper": "Opcjonalnie — np. instrukcje dostawy",
+ "checkout.coupon.title": "Masz kupon?",
+ "checkout.coupon.label": "Kod kuponu",
+ "checkout.coupon.apply": "Zastosuj kod",
+ "checkout.coupon.applying": "Stosowanie…",
+ "checkout.coupon.applied": "Kupon „{code}“ zastosowany",
+ "checkout.coupon.remove": "Usuń",
+ "checkout.coupon.error": "Nie udało się zastosować kuponu",
+ "checkout.payment.title": "Metoda płatności",
+ "checkout.payment.online": "Płatność online",
+ "checkout.payment.online_desc": "Karty i inne metody płatności obsługiwane przez Stripe.",
+ "checkout.payment.blik": "BLIK",
+ "checkout.payment.blik_desc": "Zapłać natychmiast w PLN 6-cyfrowym kodem BLIK przez Stripe.",
+ "checkout.payment.blik_label": "Kod BLIK",
+ "checkout.payment.blik_error": "Kod BLIK musi mieć 6 cyfr",
+ "checkout.payment.blik_expired": "Kod wygasł — złóż zamówienie ponownie, aby uzyskać nowe okno autoryzacji.",
+ "checkout.payment.bacs": "Przelew bankowy",
+ "checkout.payment.bacs_desc": "Zapłać przelewem — szczegóły płatności otrzymasz po złożeniu zamówienia.",
+ "checkout.payment.instructions": "Instrukcje płatności",
+ "checkout.payment.cod": "Płatność przy odbiorze",
+ "checkout.payment.cod_desc": "Zapłać gotówką przy dostawie.",
+ "checkout.payment.cheque": "Czek bankowy",
+ "checkout.payment.cheque_desc": "Wyślij czek — zamówienie wysyłamy po jego zaksięgowaniu.",
+ "checkout.payment.unavailable_digital": "Niedostępne dla zamówień cyfrowych",
+ "checkout.payment.unavailable": "Niedostępne w tej chwili",
+ "checkout.payment.ssl": "Transakcje zabezpieczone szyfrowaniem SSL",
+ "checkout.payment.unavailable_backend": "Płatności krypto są obecnie niedostępne.",
+ "checkout.payment.method_unavailable": "Wybrana metoda płatności jest niedostępna.",
+ "checkout.summary.title": "Podsumowanie zamówienia",
+ "checkout.cta": "Złóż zamówienie",
+ "checkout.terms": "Zapoznałem/am się z regulaminem serwisu i akceptuję go",
+ "checkout.privacy": "Wyrażam zgodę na przetwarzanie moich danych osobowych zgodnie z polityką prywatności",
+ "checkout.terms_link": "regulaminem",
+ "checkout.privacy_link": "polityką prywatności",
+ "checkout.error.billing_required": "Uzupełnij wymagane dane rozliczeniowe przed złożeniem zamówienia.",
+ "checkout.error.method_unavailable": "Wybrana metoda płatności jest niedostępna.",
+ "order_success.breadcrumb": "Zamówienie potwierdzone",
+ "order_success.section": "Potwierdzenie zamówienia",
+ "order_success.section_digital": "Potwierdzenie zamówienia cyfrowego",
+ "order_success.heading": "Zamówienie złożone pomyślnie",
+ "order_success.thank_you": "Dziękujemy, {name} — twoje zamówienie #{number} zostało potwierdzone. Paragon został wysłany na twój adres e-mail.",
+ "order_success.thank_you_digital": "Dziękujemy, {name} — twoje cyfrowe zamówienie #{number} zostało potwierdzone. Pliki do pobrania są gotowe poniżej, a paragon wysłaliśmy na twój e-mail.",
+ "order_success.customer_fallback": "kliencie",
+ "order_success.empty.heading": "Nie znaleziono ukończonego zamówienia",
+ "order_success.empty.message": "Ta strona pokazuje ostatnie zamówienie ukończone w tej sesji przeglądarki.",
+ "order_success.refreshing": "Odświeżanie szczegółów zamówienia",
+ "order_success.refresh_error": "Nie udało się odświeżyć danych. Wyświetlamy zamówienie zapisane podczas składania.",
+ "order_success.order_label": "Zamówienie #{number}",
+ "order_success.quantity": "Ilość: {quantity}",
+ "order_success.shipping_to": "Wysyłka do",
+ "order_success.delivery": "Dostawa",
+ "order_success.details.order": "Szczegóły zamówienia",
+ "order_success.details.delivery_payment": "Dostawa i płatność",
+ "order_success.receipt": "Potwierdzenie",
+ "order_success.currency": "Waluta",
+ "order_success.account_login_error": "Zamówienie zostało utworzone, ale automatyczna konfiguracja konta nie została ukończona: {error} Zaloguj się i sprawdź Moje zamówienia; jeśli zamówienia brakuje, skontaktuj się z obsługą.",
+ "order_success.delivery_note": "Twoje zamówienie trafiło do realizacji.",
+ "order_success.native_note": "Przejdź do natywnej strony zamówienia, aby zobaczyć szczegóły płatności i status.",
+ "order_success.payment.cod": "Płatność przy odbiorze",
+ "order_success.payment.cheque": "Płatność czekiem",
+ "order_success.payment.bacs": "Przelew bankowy",
+ "order_success.payment.crypto": "Płatność kryptowalutą",
+ "order_success.payment.blik": "BLIK",
+ "order_success.payment.card": "Płatność kartą",
+ "order_success.row.subtotal": "Suma częściowa",
+ "order_success.row.discount": "Rabat",
+ "order_success.row.discount_with_codes": "Rabat ({codes})",
+ "order_success.row.shipping": "Dostawa",
+ "order_success.row.tax": "Podatek",
+ "order_success.row.free": "Bezpłatnie",
+ "order_success.row.digital_delivery": "Dostawa cyfrowa",
+ "order_success.row.total": "Łącznie zapłacono",
+ "order_success.cta.shopping": "Kontynuuj zakupy",
+ "order_success.cta.native": "Otwórz natywną stronę zamówienia",
+ "order_success.cta.track": "Śledź zamówienie",
+ "order_success.cta.orders": "Zobacz moje zamówienia",
+ "order_success.cta.private": "Otwórz prywatną stronę zamówienia",
+ "order_success.cta.pdf": "Zapisz rachunek jako PDF",
+ "order_success.cta.pdf_hint": "Otwiera okno drukowania przeglądarki, w którym możesz zapisać rachunek jako PDF.",
+ "order_success.cta.help": "Uzyskaj pomoc dotyczącą zamówienia",
+ "order_success.promo": "Dziękujemy za ponowne zakupy — oto 15% zniżki na następne zamówienie.",
+ "order_success.support": "Pytania dotyczące zamówienia?",
+ "order_success.contact": "Skontaktuj się z nami",
+ "order_success.downloads": "Twoje pliki do pobrania",
+ "order_success.downloads_note": "Pliki są dostępne w każdej chwili w zakładce Pobrane na Twoim koncie.",
+ "order_success.download.available": "Dostępny",
+ "order_success.download.unavailable": "Niedostępny",
+ "order_success.download.expires": "Wygasa:",
+ "order_success.download.remaining": "Pozostałe pobrania:",
+ "order_success.download.unlimited": "Nieograniczone",
+ "order_success.download.cta": "Pobierz",
+ "order_success.support.trouble": "Problem z pobraniem?",
+ "order_status.pending": "Oczekuje na płatność",
+ "order_status.processing": "W realizacji",
+ "order_status.on-hold": "Wstrzymane",
+ "order_status.completed": "Zrealizowane",
+ "order_status.cancelled": "Anulowane",
+ "order_status.failed": "Nieudane",
+ "order_status.refunded": "Zwrócone",
+ "order_details.private_title": "Prywatne zamówienie",
+ "order_details.heading": "Zamówienie #{number}",
+ "order_details.guest_expires": "Ten prywatny link dla gościa wygasa {date}.",
+ "order_details.loading": "Ładowanie zamówienia",
+ "order_details.unavailable": "Nie znaleziono zamówienia, jest ono niedostępne lub 24-godzinny link dla gościa wygasł.",
+ "order_details.back_account": "Wróć do konta",
+ "auth.title.login": "Witaj ponownie",
+ "auth.title.register": "Utwórz konto",
+ "auth.title.forgot": "Zapomniane hasło",
+ "auth.breadcrumb.login": "Zaloguj się",
+ "auth.breadcrumb.register": "Rejestracja",
+ "auth.breadcrumb.forgot": "Zapomniane hasło",
+ "auth.desc.login": "Zaloguj się, aby śledzić zamówienia, zarządzać listą życzeń i realizować zakupy szybciej.",
+ "auth.desc.register": "Dołącz do Superfunky i korzystaj z spersonalizowanych rekomendacji.",
+ "auth.desc.forgot": "Wyślemy Ci bezpieczny link do odzyskania dostępu do konta.",
+ "auth.tab.login": "Logowanie",
+ "auth.tab.register": "Rejestracja",
+ "auth.tab.forgot": "Zapomniane hasło",
+ "auth.password_updated": "Hasło zostało zaktualizowane. Zaloguj się nowym hasłem.",
+ "auth.or_continue": "Lub kontynuuj z",
+ "auth.continue_with": "Kontynuuj z {provider}",
+ "auth.brand_tagline": "Nowoczesne doświadczenie zakupowe połączone z Twoim kontem w serwisie.",
+ "auth.login.username": "Nazwa użytkownika lub e-mail",
+ "auth.login.password": "Hasło",
+ "auth.login.remember": "Zapamiętaj mnie",
+ "auth.login.forgot_link": "Zapomniane hasło?",
+ "auth.login.error": "Logowanie nie powiodło się.",
+ "auth.login.cta": "Zaloguj się",
+ "auth.login.cta_loading": "Logowanie…",
+ "auth.register.first_name": "Imię",
+ "auth.register.last_name": "Nazwisko",
+ "auth.register.username": "Nazwa użytkownika",
+ "auth.register.username_placeholder": "np. funk_rider.99",
+ "auth.register.username_helper": "Używana w adresie URL profilu społeczności — litery, cyfry, podkreślenia, myślniki, kropki",
+ "auth.register.email": "E-mail",
+ "auth.register.password": "Hasło",
+ "auth.register.password_helper": "Co najmniej 8 znaków",
+ "auth.register.confirm": "Potwierdź hasło",
+ "auth.register.newsletter": "Chcę otrzymywać okazjonalne e-maile o nowych produktach, ofertach i wznowieniach. Możesz zrezygnować w dowolnym momencie.",
+ "auth.register.error": "Nie udało się utworzyć konta.",
+ "auth.register.cta": "Utwórz konto",
+ "auth.register.cta_loading": "Tworzenie konta…",
+ "auth.register.success.heading": "Konto utworzone.",
+ "auth.register.success.body": "Możesz teraz zalogować się swoim adresem e-mail i hasłem.",
+ "auth.register.success.cta": "Przejdź do logowania",
+ "auth.forgot.field": "Nazwa użytkownika lub e-mail",
+ "auth.forgot.error": "Nie udało się wysłać e-maila z resetem hasła.",
+ "auth.forgot.cta": "Wyślij link resetujący",
+ "auth.forgot.cta_loading": "Wysyłanie…",
+ "auth.forgot.success": "Jeśli konto dla {identity} istnieje, link resetujący jest już w drodze.",
+ "auth.reset.breadcrumb": "Resetowanie hasła",
+ "auth.reset.error.mismatch": "Hasła nie są identyczne.",
+ "auth.reset.error.failed": "Nie udało się zresetować hasła.",
+ "auth.oauth.error": "Logowanie przez dostawcę nie powiodło się.",
+ "auth.oauth.callback_error": "Wywołanie zwrotne dostawcy jest niekompletne lub nieprawidłowe.",
+ "validation.passwords_mismatch": "Hasła nie są identyczne",
+ "validation.password.min_length": "Użyj co najmniej 8 znaków.",
+ "validation.password.uppercase": "Dodaj wielką literę.",
+ "validation.password.lowercase": "Dodaj małą literę.",
+ "validation.password.number": "Dodaj cyfrę.",
+ "validation.password.special": "Dodaj znak specjalny.",
+ "validation.required": "{label} jest wymagane.",
+ "validation.min_length": "{label} musi mieć co najmniej {min} znaki.",
+ "validation.max_length": "{label} może mieć co najwyżej {max} znaki.",
+ "validation.email": "{label} nie jest prawidłowym adresem e-mail.",
+ "validation.phone": "{label} nie jest prawidłowym numerem telefonu.",
+ "validation.username_chars": "{label} może zawierać tylko litery, cyfry, podkreślenia, myślniki i kropki.",
+ "account.tab.dashboard": "Panel",
+ "account.tab.orders": "Zamówienia",
+ "account.tab.addresses": "Adresy",
+ "account.tab.community": "Społeczność",
+ "account.logout": "Wyloguj",
+ "account.signin_register": "Zaloguj się lub zarejestruj",
+ "account.guest.name": "Konto gościa",
+ "account.guest.email_placeholder": "Zaloguj się, aby wczytać konto",
+ "account.loading": "Ładowanie konta",
+ "account.empty_state": "Zaloguj się, aby wczytać profil i podsumowanie konta.",
+ "account.greeting": "Cześć, {name} 👋",
+ "account.role": "Rola: {role}",
+ "account.verified": "Konto zweryfikowane",
+ "account.orders.empty": "Brak zamówień.",
+ "account.orders.loading": "Ładowanie zamówień…",
+ "account.orders.sign_in": "Zaloguj się, aby zobaczyć historię zamówień.",
+ "account.orders.view_details": "Zobacz szczegóły",
+ "account.addresses.loading": "Ładowanie zapisanych adresów…",
+ "account.addresses.sign_in": "Zaloguj się, aby zarządzać adresami rozliczeniowymi i wysyłki.",
+ "account.addresses.save": "Zapisz adres",
+ "account.addresses.save_error": "Nie udało się zapisać adresu.",
+ "account.guest.benefit1_eyebrow": "Twoje konto",
+ "account.guest.benefit1_title": "Zarządzaj wszystkim w jednym miejscu",
+ "account.guest.benefit2_eyebrow": "Historia zamówień",
+ "account.guest.benefit2_title": "Śledź każde zamówienie",
+ "account.guest.benefit3_eyebrow": "Szybsza realizacja",
+ "account.guest.benefit3_title": "Zapisz dane do rozliczeń i dostawy",
+ "account.guest.benefit4_eyebrow": "Społeczność i rynek",
+ "account.guest.benefit4_title": "Odblokuj narzędzia przypisane do Twojej roli",
+ "account.guest.cta.login": "Zaloguj się",
+ "account.guest.cta.register": "Utwórz konto",
+ "product.add_to_cart": "Dodaj do koszyka",
+ "product.added": "Dodano ✓",
+ "product.buy_now": "Kup teraz",
+ "product.choose_options": "Wybierz dostępne opcje",
+ "product.select_options": "Wybierz opcje",
+ "product.grouped": "Produkt grupowany",
+ "product.add_wishlist": "Dodaj do listy życzeń",
+ "product.remove_wishlist": "Usuń z listy życzeń",
+ "product.save_reading": "Zapisz na liście lektur",
+ "product.remove_reading": "Usuń z listy lektur",
+ "product.unavailable": "Produkt niedostępny",
+ "product.loading": "Ładowanie produktu",
+ "product.related_loading": "Ładowanie podobnych produktów",
+ "search.placeholder": "Szukaj produktów, artykułów, osób i tagów…",
+ "search.open": "Otwórz wyszukiwanie",
+ "search.close": "Zamknij wyszukiwanie",
+ "header.theme.toggle": "Przełącz tryb kolorów",
+ "header.theme.light": "Przełącz na jasny motyw",
+ "header.theme.dark": "Przełącz na ciemny motyw",
+ "header.push.enable": "Włącz powiadomienia push",
+ "header.push.disable": "Wyłącz powiadomienia push",
+ "header.push.enabled": "Powiadomienia push są włączone",
+ "header.account": "Konto",
+ "header.reading_list": "Czytelnia",
+ "header.wishlist": "Lista życzeń",
+ "header.cart": "Koszyk",
+ "header.sync_error": "{label} (błąd synchronizacji)",
+ "header.sync_error_detail": "{label} — {message}",
+ "header.menu.open": "Otwórz menu",
+ "header.menu.site": "Menu witryny",
+ "header.menu.title": "Menu",
+ "header.menu.close": "Zamknij menu",
+ "header.navigation.main": "Główna nawigacja",
+ "header.navigation.mobile": "Nawigacja mobilna",
+ "search.aria": "Wyniki wyszukiwania",
+ "search.results": "Wyniki wyszukiwania",
+ "search.loading": "Przeszukiwanie witryny…",
+ "search.no_results": "Brak wyników dla „{query}”",
+ "search.unavailable": "Wyszukiwanie jest niedostępne",
+ "search.type.product": "Produkt",
+ "search.type.post": "Wpis",
+ "search.type.page": "Strona",
+ "search.type.post_category": "Kategoria wpisów",
+ "search.type.post_tag": "Tag wpisów",
+ "search.type.product_category": "Kategoria produktów",
+ "search.type.product_tag": "Tag produktów",
+ "search.type.product_brand": "Marka produktu",
+ "search.type.author": "Autor",
+ "search.type.community_post": "Wpis społeczności",
+ "search.type.community_author": "Członek społeczności",
+ "search.type.community_tag": "Tag społeczności",
+ "search.group.catalog": "Katalog",
+ "search.group.editorial": "Artykuły",
+ "search.group.community": "Społeczność",
+ "search.group.pages": "Strony",
+ "nav.home": "Strona główna",
+ "nav.main_aria": "Główna nawigacja",
+ "nav.mobile_aria": "Nawigacja mobilna",
+ "nav.site_aria": "Menu witryny",
+ "nav.open_menu": "Otwórz menu",
+ "nav.close_menu": "Zamknij menu",
+ "nav.account": "Konto",
+ "nav.cart": "Koszyk",
+ "nav.wishlist": "Lista życzeń",
+ "nav.reading_list": "Lista lektur",
+ "nav.toggle_dark": "Przełącz tryb ciemny",
+ "nav.select_currency": "Wybierz walutę",
+ "nav.select_language": "Wybierz język",
+ "nav.close_newsletter": "Zamknij zapis do newslettera",
+ "nav.close_quickview": "Zamknij szybki podgląd",
+ "nav.breadcrumb": "Ścieżka nawigacji",
+ "image.close": "Zamknij przeglądarkę zdjęć",
+ "image.next": "Następne zdjęcie",
+ "image.prev": "Poprzednie zdjęcie",
+ "image.viewer_aria": "Przeglądarka zdjęć",
+ "cookie.title": "Zgoda na pliki cookie",
+ "cookie.settings": "Ustawienia plików cookie",
+ "cookie.manage": "Zarządzaj preferencjami plików cookie",
+ "wishlist.loading": "Ładowanie zapisanych produktów",
+ "wishlist.empty": "Brak pozycji.",
+ "reading_list.loading": "Ładowanie zapisanych artykułów",
+ "reading_list.empty": "Brak artykułów.",
+ "community.share": "Udostępnij nowy wpis",
+ "community.write": "Napisz nowy artykuł",
+ "community.list_product": "Dodaj nowy produkt",
+ "community.profile.sections": "Sekcje profilu",
+ "community.copy_link": "Kopiuj link",
+ "loading.page": "Ładowanie strony",
+ "loading.content": "Ładowanie zawartości",
+ "loading.post": "Ładowanie wpisu",
+ "loading.product": "Ładowanie produktu",
+ "loading.community_post": "Ładowanie wpisu społeczności",
+ "loading.community_feed": "Ładowanie aktualności społeczności",
+ "loading.community_profile": "Ładowanie profilu społeczności",
+ "error.page_unavailable": "Strona niedostępna",
+ "error.post_unavailable": "Wpis niedostępny",
+ "error.product_unavailable": "Produkt niedostępny",
+ "error.community_post_unavailable": "Wpis społeczności niedostępny",
+ "error.author_unavailable": "Autor niedostępny",
+ "error.archive_unavailable": "Archiwum niedostępne",
+ "error.content_unavailable": "Zawartość niedostępna",
+ "error.like_failed": "Nie udało się zaktualizować polubienia.",
+ "error.review_failed": "Nie udało się wysłać recenzji.",
+ "error.reply_failed": "Nie udało się wysłać odpowiedzi.",
+ "error.article_failed": "Nie udało się opublikować artykułu.",
+ "error.post_failed": "Nie udało się opublikować wpisu.",
+ "error.product_listing_failed": "Nie udało się dodać produktu.",
+ "error.account_load": "Nie udało się wczytać konta",
+ "notification.dismiss": "Zamknij powiadomienie",
+ "notification.push.enable_error": "Nie udało się włączyć powiadomień push.",
+ "notification.push.disable_error": "Nie udało się wyłączyć powiadomień push.",
+ "newsletter.join": "Dołącz do listy mailingowej",
+ "newsletter.email": "E-mail",
+ "newsletter.email_placeholder": "twoj@email.pl",
+ "newsletter.subscribe": "Zapisz się",
+ "newsletter.subscribing": "Zapisywanie…",
+ "newsletter.subscribed": "Dziękujemy. Subskrypcja jest aktywna.",
+ "newsletter.signup_error": "Nie udało się zapisać do newslettera.",
+ "newsletter.unsubscribe": "Wypisz się",
+ "newsletter.consent": "Zaakceptuj notę prywatności, aby kontynuować.",
+ "newsletter.email_invalid": "Wprowadź prawidłowy adres e-mail.",
+ "footer.newsletter.title": "Otrzymuj premiery produktów i oferty jako pierwszy",
+ "footer.newsletter.description": "Zapisz się po wybrane propozycje, informacje o premierach i comiesięczne aktualizacje. Bez spamu, możesz zrezygnować w każdej chwili.",
+ "footer.newsletter.privacy": "Zgadzam się na otrzymywanie wiadomości marketingowych i akceptuję politykę prywatności.",
+ "footer.assistant.title": "Asystent zakupowy AI — zapytaj o rozmiary, zamówienia lub rekomendacje",
+ "footer.assistant.tab": "Asystent zakupowy AI",
+ "footer.spotify.tab": "Odtwarzacz Spotify",
+ "footer.assistant_spotify.aria": "Asystent / Spotify",
+ "footer.radio.title": "Superfunky Radio",
+ "footer.radio.description": "Instrumentalny jazz-hop do przeglądania — wybierz dowolny utwór, album lub podcast.",
+ "footer.nav.expand": "Rozwiń {label}",
+ "footer.nav.collapse": "Zwiń {label}",
+ "cookie.banner.accept_all": "Zaakceptuj wszystkie",
+ "cookie.banner.description": "{providerName} korzysta z plików cookie w celu prawidłowego działania naszej witryny, a także do celów analitycznych i reklamowych. Dowiedz się więcej w naszej",
+ "cookie.banner.policy_link": "Polityce cookies",
+ "cookie.category.functional": "Funkcjonalne",
+ "cookie.category.functional_desc": "Zawsze aktywne",
+ "cookie.category.functional_title": "Wymagane dla podstawowych funkcji — koszyka, listy życzeń oraz zapamiętywania tego wyboru.",
+ "cookie.category.marketing": "Marketingowe",
+ "cookie.category.marketing_desc": "Reklamy",
+ "cookie.category.tracking": "Analityczne",
+ "cookie.category.tracking_desc": "Analityka",
+ "cookie.category.performance": "Wydajnościowe",
+ "cookie.category.performance_desc": "Układ",
+ "cookie.item.delete": "Usuń",
+ "cookie.item.delete_aria": "Usuń {name}",
+ "cookie.item.expires_in": "Wygasa za {lifetime}",
+ "cookie.item.required": "Wymagane",
+ "cookie.manager.accept": "Akceptuj",
+ "cookie.manager.close": "Zamknij",
+ "cookie.manager.decline": "Odrzuć",
+ "cookie.manager.nothing_to_show": "Nie ma już nic do wyświetlenia.",
+ "cookie.manager.save": "Zapisz",
+ "cookie.manager.subtitle": "Wybierz, z których opcjonalnych plików cookie możemy korzystać.",
+ "cookie.manager.tab_cookies": "Lista plików cookie",
+ "cookie.manager.tab_preferences": "Preferencje",
+ "filters.all_authors": "Wszyscy autorzy",
+ "filters.all_brands": "Wszystkie marki",
+ "filters.all_categories": "Wszystkie kategorie",
+ "filters.all_tags": "Wszystkie tagi",
+ "filters.aria_label": "Filtry: {title}",
+ "filters.author_aria": "Filtruj według autora",
+ "filters.brand_aria": "Filtruj według marki",
+ "filters.browse": "Przeglądaj",
+ "filters.category_aria": "Filtruj według kategorii",
+ "filters.clear": "Wyczyść filtry",
+ "filters.default_empty_social": "Brak postów — bądź pierwszą osobą, która się tu podzieli.",
+ "filters.default_title_posts": "Najnowsze posty",
+ "filters.default_title_products": "Produkty",
+ "filters.default_title_social": "Kanał społeczności",
+ "filters.end_reached": "To już koniec listy.",
+ "filters.interested_in": "Zainteresowania",
+ "filters.layout_compact": "Siatka kompaktowa (styl profilu)",
+ "filters.layout_grid3": "3 kolumny",
+ "filters.layout_grid4": "4 kolumny",
+ "filters.layout_label": "Układ",
+ "filters.layout_list": "Lista",
+ "filters.layout_masonry": "Układ murarski",
+ "filters.load_mode_infinite": "Nieskończone przewijanie",
+ "filters.load_mode_pages": "Strony",
+ "filters.load_more": "Wczytaj więcej",
+ "filters.loading_more": "Wczytywanie kolejnych…",
+ "filters.next": "Dalej →",
+ "filters.no_posts_match": "Żadne posty nie pasują do tych filtrów.",
+ "filters.no_products_match": "Żadne produkty nie pasują do tych filtrów.",
+ "filters.pagination_aria": "Paginacja: {title}",
+ "filters.prev": "← Wstecz",
+ "filters.rating_3": "3+ gwiazdki",
+ "filters.rating_4": "4+ gwiazdki",
+ "filters.rating_45": "4,5+ gwiazdki",
+ "filters.rating_any": "Dowolna ocena",
+ "filters.rating_aria": "Filtruj według minimalnej oceny",
+ "filters.search_feed": "Szukaj w kanale",
+ "filters.search_posts": "Szukaj postów",
+ "filters.search_products": "Szukaj produktów",
+ "filters.showing_label": "Wyświetlono",
+ "filters.showing_suffix": "z {total}",
+ "filters.sort_default_order": "Domyślna kolejność",
+ "filters.sort_featured": "Polecane",
+ "filters.sort_feed_aria": "Sortuj kanał",
+ "filters.sort_name": "Nazwa",
+ "filters.sort_newest": "Najnowsze",
+ "filters.sort_oldest": "Najstarsze",
+ "filters.sort_popular": "Najbardziej polubione",
+ "filters.sort_posts_aria": "Sortuj posty",
+ "filters.sort_price_asc": "Cena: od najniższej",
+ "filters.sort_price_desc": "Cena: od najwyższej",
+ "filters.sort_products_aria": "Sortuj produkty",
+ "filters.sort_rating": "Najwyżej oceniane",
+ "filters.sort_title": "Tytuł",
+ "filters.tag_aria": "Filtruj według tagu",
+ "image.show_label": "Pokaż {label}",
+ "newsletter.default_body": "Dołącz do naszej listy insiderów, aby zyskać wcześniejszy dostęp, ekskluzywne oferty i wyselekcjonowane historie ze świata Superfunky.",
+ "newsletter.default_title": "Bądź pierwszy, który dowie się o nowej ulubionej premierze.",
+ "newsletter.eyebrow": "Bądź na bieżąco",
+ "newsletter.image_placeholder_body": "Umieść tutaj pełnowymiarowe zdjęcie produktu lub materiał redakcyjny.",
+ "newsletter.image_placeholder_title": "Miejsce na obraz",
+ "newsletter.maybe_later": "Może później",
+ "newsletter.subscribed_body": "Dziękujemy za zapisanie się — wkrótce otrzymasz pierwszy podgląd naszej kolejnej premiery oraz ekskluzywne aktualizacje.",
+ "newsletter.trust.easy_unsubscribe": "Łatwa rezygnacja",
+ "newsletter.trust.no_spam": "Bez spamu",
+ "newsletter.trust.privacy": "Szanujemy prywatność",
+ "order_success.download.downloading": "Pobieranie…",
+ "order_success.download.empty": "To zamówienie nie zawiera żadnych plików do pobrania.",
+ "order_success.download.error": "Pobieranie pliku nie powiodło się. Spróbuj ponownie lub skontaktuj się z pomocą techniczną.",
+ "order_success.download.loading": "Wczytywanie bezpiecznych linków do pobrania…",
+ "order_success.download.never": "Nigdy",
+ "product.cta.gallery_thumbnails_aria": "Miniatury galerii: {name}",
+ "product.cta.learn_more": "Dowiedz się więcej",
+ "product.cta.quick_view": "Szybki podgląd",
+ "product.cta.quick_view_aria": "Szybki podgląd — {name}",
+ "product.cta.show_photo_aria": "Pokaż zdjęcie {index}",
+ "product.cta.view_product": "Zobacz produkt",
+ "product.cta.view_product_aria": "Zobacz {name}",
+ "product.cta.view_products": "Zobacz produkty",
+ "product.image_alt": "Zdjęcie produktu",
+ "product.image_alt_indexed": "Zdjęcie produktu {index}",
+ "product.status.available": "Dostępny",
+ "product.status.new": "Nowość",
+ "product.status.promotion": "Promocja",
+ "product.status.promotion_percent": "Promocja {percent}%",
+ "product.status.sold_out": "Wyprzedane",
+ "product.variation_unavailable.description": "Wybierz kombinację opcji dostępną w magazynie.",
+ "product.variation_unavailable.title": "Wariant niedostępny",
+ "reading_list.browse_blog": "Przeglądaj bloga",
+ "reading_list.cap_error": "Nie udało się wczytać limitu listy lektur: {error}",
+ "reading_list.cap_suffix": "z {cap}",
+ "reading_list.category_fallback": "Dziennik",
+ "reading_list.count": "Zapisano {count} {item}",
+ "reading_list.empty_hint": "Użyj przycisku zakładki przy dowolnym artykule na blogu, aby zapisać go do przeczytania później.",
+ "reading_list.item_plural": "artykuły",
+ "reading_list.item_singular": "artykuł",
+ "reading_list.load_error": "Nie udało się wczytać zapisanych artykułów: {message}",
+ "reading_list.mark_read": "Oznacz jako przeczytane",
+ "reading_list.read": "Przeczytane",
+ "reading_list.reading_time": "{minutes} min czytania",
+ "reading_list.sign_in_suffix": ", aby zsynchronizować listę lektur na wszystkich urządzeniach.",
+ "reading_list.sync_error": "Nie udało się zsynchronizować listy lektur: {error}",
+ "reading_list.sync_local": "zapisywana lokalnie w tej przeglądarce",
+ "reading_list.sync_synced": "zsynchronizowana z Twoim kontem",
+ "reading_list.unread_title": "Nieprzeczytane",
+ "share.label": "Udostępnij",
+ "share.link_copied": "Link skopiowany!",
+ "share.on.facebook": "Udostępnij na Facebooku",
+ "share.on.linkedin": "Udostępnij na LinkedIn",
+ "share.on.telegram": "Udostępnij na Telegramie",
+ "share.on.tiktok": "Udostępnij na TikToku",
+ "share.on.whatsapp": "Udostępnij na WhatsApp",
+ "share.on.x": "Udostępnij na X",
+ "share.via_email": "Udostępnij przez e-mail",
+ "wishlist.browse_shop": "Przeglądaj sklep",
+ "wishlist.cap_error": "Nie udało się wczytać limitu listy życzeń: {error}",
+ "wishlist.cap_suffix": "z {cap}",
+ "wishlist.clear_unavailable": "Usuń niedostępne produkty",
+ "wishlist.count": "Zapisano {count} {item}",
+ "wishlist.empty_hint": "Dotknij ikony serca na dowolnej karcie produktu, aby zapisać go tutaj na później — pozostanie zapisany przy kolejnych wizytach.",
+ "wishlist.item_plural": "produkty",
+ "wishlist.item_singular": "produkt",
+ "wishlist.load_error": "Nie udało się wczytać zapisanych produktów: {message}",
+ "wishlist.sign_in_suffix": ", aby zsynchronizować listę życzeń na wszystkich urządzeniach.",
+ "wishlist.sync_error": "Nie udało się zsynchronizować listy życzeń: {error}",
+ "wishlist.sync_local": "zapisywana lokalnie w tej przeglądarce",
+ "wishlist.sync_synced": "zsynchronizowana z Twoim kontem",
+ "wishlist.unavailable_message": "Te zapisane produkty nie są już dostępne w bieżącym katalogu.",
+ "account.tab.downloads": "Pliki do pobrania",
+ "account.avatar.saving": "Zapisywanie awatara…",
+ "account.avatar.change": "Zmień awatar",
+ "account.avatar.add": "Dodaj awatar",
+ "account.avatar.remove": "Usuń awatar",
+ "account.avatar.max_size": "Maksymalny rozmiar pliku to 690 KB",
+ "account.email.verification_required": "Wymagana weryfikacja adresu e-mail",
+ "account.email.verification_optional": "Weryfikacja adresu e-mail jest opcjonalna",
+ "account.newsletter.subscribed": "Zapisano do newslettera",
+ "account.newsletter.not_subscribed": "Brak zapisu do newslettera",
+ "account.stat.orders_placed": "Złożone zamówienia",
+ "account.stat.saved_addresses": "Zapisane adresy",
+ "account.stat.publishing_role": "Rola publikacyjna",
+ "account.role.member": "Członek",
+ "account.guest.benefit1_description": "Zaloguj się, aby zobaczyć swój zweryfikowany profil oraz prywatne narzędzia klienta.",
+ "account.guest.benefit1_item1": "Przeglądaj swój profil i status konta",
+ "account.guest.benefit1_item2": "Zobacz zamówienia i zapisane dane dostawy w jednym miejscu",
+ "account.guest.benefit1_item3": "Odkryj narzędzia publikacyjne dostępne dla Twojej roli",
+ "account.guest.benefit2_description": "Historia zamówień jest prywatna. Zaloguj się, aby zobaczyć rzeczywiste statusy zamówień, kwoty, produkty i szczegóły wariantów.",
+ "account.guest.benefit2_item1": "Sprawdź aktualny status realizacji",
+ "account.guest.benefit2_item2": "Przeglądaj pozycje zamówienia i warianty",
+ "account.guest.benefit2_item3": "Miej dostęp do wcześniejszych zakupów do wglądu",
+ "account.guest.benefit3_description": "Utwórz konto, aby bezpiecznie zarządzać profilem klienta oraz adresami do zamówień.",
+ "account.guest.benefit3_item1": "Edytuj osobno adres rozliczeniowy i dostawy",
+ "account.guest.benefit3_item2": "Ponownie wykorzystuj dokładne dane klienta",
+ "account.guest.benefit3_item3": "Zachowaj dane adresowe prywatne w ramach swojego konta",
+ "account.guest.benefit4_description": "Zaloguj się, aby zarządzać swoim publicznym profilem oraz — jeśli masz uprawnienia — korzystać z funkcji publikacyjnych Twórcy, Współtwórcy lub administratora.",
+ "account.guest.benefit4_item1": "Kontroluj widoczność publicznego profilu",
+ "account.guest.benefit4_item2": "Publikuj posty w społeczności, jeśli masz uprawnienia",
+ "account.guest.benefit4_item3": "Wystawiaj produkty i pisz artykuły, jeśli masz uprawnienia",
+ "account.guest.benefit5_eyebrow": "Bezpieczna biblioteka cyfrowa",
+ "account.guest.benefit5_title": "Miej stały dostęp do zakupionych plików",
+ "account.guest.benefit5_description": "Zaloguj się, aby uzyskać dostęp do plików do pobrania dostępnych na Twoim koncie.",
+ "account.guest.benefit5_item1": "Korzystaj z podpisanych linków do pobrania",
+ "account.guest.benefit5_item2": "Sprawdzaj terminy ważności i pozostałe limity",
+ "account.guest.benefit5_item3": "Miej zakupy powiązane ze swoim kontem",
+ "community.profile.unavailable": "Profil społeczności niedostępny",
+ "community.profile.private_badge": "Prywatny",
+ "community.role.collaborator": "Współtwórca",
+ "community.role.creator": "Twórca",
+ "community.profile.own_badge": "To Ty",
+ "community.follow.following": "Obserwujesz",
+ "community.follow.requested": "Wysłano prośbę",
+ "community.follow.cta": "Obserwuj",
+ "community.stat.posts": "Posty",
+ "community.stat.articles": "Artykuły",
+ "community.stat.followers": "Obserwujący",
+ "community.stat.following": "Obserwowani",
+ "community.stat.listings": "Oferty",
+ "community.feed.title": "Kanał społeczności",
+ "community.title": "Społeczność",
+ "community.authors": "Autorzy społeczności",
+ "community.tab.posts": "Posty ({count})",
+ "community.tab.shop": "Sklep ({count})",
+ "community.tab.articles": "Artykuły ({count})",
+ "community.tab.followers": "Obserwujący ({count})",
+ "community.tab.following": "Obserwowani ({count})",
+ "community.profile.private": "Ten profil jest prywatny",
+ "community.feed.following_title": "Posty od obserwowanych profili",
+ "community.feed.empty_following": "Brak jeszcze postów od obserwowanych profili.",
+ "community.shop.title": "Twój sklep",
+ "community.shop.manage": "Zarządzaj swoimi ofertami",
+ "community.remove": "Usuń",
+ "community.shop.empty": "Brak jeszcze ofert.",
+ "community.articles.title": "Twoje artykuły",
+ "community.articles.manage": "Zarządzaj swoimi artykułami",
+ "community.articles.empty": "Brak jeszcze artykułów.",
+ "community.posts.empty": "Brak jeszcze postów.",
+ "community.media.read_error": "Nie udało się odczytać wybranych plików multimedialnych.",
+ "community.translation.search_error": "Wyszukiwanie tłumaczeń nie powiodło się.",
+ "community.post.updated": "Post zaktualizowany",
+ "community.post.published": "Post opublikowany",
+ "community.changes_live": "Twoje zmiany są już widoczne.",
+ "community.post.live": "Twój post w społeczności jest już widoczny.",
+ "community.modal.close": "Zamknij",
+ "community.post.edit_title": "Edytuj post w społeczności",
+ "community.post.create_title": "Udostępnij nowy post",
+ "community.media.add_more": "Dodaj więcej plików multimedialnych",
+ "community.media.choose": "Wybierz zdjęcia lub filmy MP4",
+ "community.field.title": "Tytuł",
+ "community.post.title_placeholder": "Nadaj postowi jasny tytuł",
+ "community.field.description": "Opis",
+ "community.post.description_placeholder": "Dodaj kontekst, szczegóły lub opowieść",
+ "community.field.tags": "Tagi",
+ "community.saving": "Zapisywanie…",
+ "community.publishing": "Publikowanie…",
+ "community.save_changes": "Zapisz zmiany",
+ "community.post.submit": "Opublikuj",
+ "community.article.updated": "Artykuł zaktualizowany",
+ "community.article.published": "Artykuł opublikowany",
+ "community.article.live": "Jest już widoczny w dzienniku witryny.",
+ "community.article.delete_confirm": "Trwale usunąć ten artykuł? Tej czynności nie można cofnąć.",
+ "community.article.deleted": "Artykuł usunięty",
+ "community.article.deleted_body": "Artykuł został usunięty.",
+ "community.article.delete_error": "Nie udało się usunąć artykułu.",
+ "community.article.edit_title": "Edytuj artykuł",
+ "community.article.create_title": "Napisz nowy artykuł",
+ "community.article.edit_description": "Zaktualizuj poniższe dane — zmiany zostaną od razu opublikowane w dzienniku witryny.",
+ "community.article.create_description": "Konta Współtwórców mogą publikować ten artykuł bezpośrednio w dzienniku witryny.",
+ "community.field.slug": "Slug",
+ "community.field.excerpt": "Zajawka",
+ "community.field.body": "Treść",
+ "community.publish": "Opublikuj",
+ "community.deleting": "Usuwanie…",
+ "community.delete": "Usuń",
+ "community.product.image_read_error": "Nie udało się odczytać obrazu.",
+ "community.product.file_read_error": "Nie udało się odczytać pliku.",
+ "community.product.updated": "Produkt zaktualizowany",
+ "community.product.published": "Produkt wystawiony",
+ "community.product.live": "Jest już widoczny w Twoim sklepie oraz na rynku społeczności.",
+ "community.product.edit_title": "Edytuj produkt",
+ "community.product.create_title": "Wystaw nowy produkt",
+ "community.product.edit_description": "Zaktualizuj poniższe dane — zmiany zostaną od razu opublikowane w Twoim sklepie na rynku.",
+ "community.product.create_description": "Konta Współtwórców mogą publikować ten produkt bezpośrednio w swoim sklepie na rynku.",
+ "community.product.media.add_more": "Dodaj więcej zdjęć produktu",
+ "community.product.media.choose": "Wybierz zdjęcia produktu",
+ "community.product.field.name": "Nazwa produktu",
+ "community.product.field.brand": "Marka",
+ "community.product.field.subtitle": "Podtytuł",
+ "community.product.field.type": "Typ produktu",
+ "community.product.field.external_url": "Zewnętrzny adres URL produktu",
+ "community.product.field.button_text": "Tekst przycisku",
+ "community.product.field.sku": "SKU",
+ "community.product.field.stock": "Stan magazynowy",
+ "community.product.field.category": "Kategoria",
+ "community.product.submit": "Wystaw produkt"
+}
diff --git a/assets/submission-form-block.js b/assets/submission-form-block.js
new file mode 100644
index 0000000..cc6315d
--- /dev/null
+++ b/assets/submission-form-block.js
@@ -0,0 +1,42 @@
+( function ( blocks, blockEditor, components, element, i18n ) {
+ const el = element.createElement;
+ const TextControl = components.TextControl;
+ const ToggleControl = components.ToggleControl;
+ const InspectorControls = blockEditor.InspectorControls;
+ const PanelBody = components.PanelBody;
+
+ blocks.registerBlockType( 'funkycommerce/submission-form', {
+ title: i18n.__( 'FunkyCommerce Form', 'funkycommerce-headless' ),
+ icon: 'feedback',
+ category: 'widgets',
+ description: i18n.__( 'A backend-stored contact form with optional protected attachments.', 'funkycommerce-headless' ),
+ edit: function ( props ) {
+ const attributes = props.attributes;
+ const set = props.setAttributes;
+ return el(
+ element.Fragment,
+ null,
+ el(
+ InspectorControls,
+ null,
+ el(
+ PanelBody,
+ { title: i18n.__( 'Form settings', 'funkycommerce-headless' ) },
+ el( TextControl, { label: i18n.__( 'Form ID', 'funkycommerce-headless' ), value: attributes.formId, onChange: function ( value ) { set( { formId: value } ); } } ),
+ el( TextControl, { label: i18n.__( 'Form name', 'funkycommerce-headless' ), value: attributes.formName, onChange: function ( value ) { set( { formName: value } ); } } ),
+ el( ToggleControl, { label: i18n.__( 'Allow attachments', 'funkycommerce-headless' ), checked: attributes.uploads, onChange: function ( value ) { set( { uploads: value } ); } } )
+ )
+ ),
+ el(
+ 'div',
+ { className: props.className },
+ el( TextControl, { label: i18n.__( 'Heading', 'funkycommerce-headless' ), value: attributes.title, onChange: function ( value ) { set( { title: value } ); } } ),
+ el( 'p', null, i18n.__( 'The storefront renders Name, Email, Message, and optional attachment fields.', 'funkycommerce-headless' ) )
+ )
+ );
+ },
+ save: function () {
+ return null;
+ },
+ } );
+} )( window.wp.blocks, window.wp.blockEditor, window.wp.components, window.wp.element, window.wp.i18n );
diff --git a/assets/video-hero-block.js b/assets/video-hero-block.js
new file mode 100644
index 0000000..1c305b9
--- /dev/null
+++ b/assets/video-hero-block.js
@@ -0,0 +1,70 @@
+( function ( blocks, blockEditor, components, element, i18n, serverSideRender ) {
+ var el = element.createElement;
+ var InspectorControls = blockEditor.InspectorControls;
+ var TextControl = components.TextControl;
+ var TextareaControl = components.TextareaControl;
+ var RangeControl = components.RangeControl;
+ var ToggleControl = components.ToggleControl;
+ var SelectControl = components.SelectControl;
+
+ blocks.registerBlockType( 'funkycommerce/video-hero', {
+ apiVersion: 3,
+ title: i18n.__( 'Video hero/banner', 'funkycommerce-headless' ),
+ icon: 'format-video',
+ category: 'design',
+ attributes: {
+ src: { type: 'string', default: '' },
+ variant: { type: 'string', default: 'fullbleed' },
+ poster: { type: 'string', default: '' },
+ kicker: { type: 'string', default: '' },
+ title: { type: 'string', default: 'Video hero' },
+ description: { type: 'string', default: '' },
+ primaryCtaLabel: { type: 'string', default: '' },
+ primaryCtaHref: { type: 'string', default: '' },
+ secondaryCtaLabel: { type: 'string', default: '' },
+ secondaryCtaHref: { type: 'string', default: '' },
+ align: { type: 'string', default: 'left' },
+ height: { type: 'string', default: '70vh' },
+ overlayOpacity: { type: 'number', default: 55 },
+ autoplay: { type: 'boolean', default: true },
+ loop: { type: 'boolean', default: true },
+ muted: { type: 'boolean', default: true }
+ },
+ edit: function ( props ) {
+ var a = props.attributes;
+ function field( name ) {
+ return function ( value ) {
+ var update = {};
+ update[ name ] = value;
+ props.setAttributes( update );
+ };
+ }
+ return el( element.Fragment, {},
+ el( InspectorControls, {},
+ el( components.PanelBody, { title: i18n.__( 'Media', 'funkycommerce-headless' ), initialOpen: true },
+ el( TextControl, { label: i18n.__( 'Video URL (MP4, WebM, YouTube, or Vimeo)', 'funkycommerce-headless' ), value: a.src, onChange: field( 'src' ) } ),
+ el( TextControl, { label: i18n.__( 'Poster/fallback image URL', 'funkycommerce-headless' ), value: a.poster, onChange: field( 'poster' ) } ),
+ el( ToggleControl, { label: i18n.__( 'Autoplay', 'funkycommerce-headless' ), checked: a.autoplay, onChange: field( 'autoplay' ) } ),
+ el( ToggleControl, { label: i18n.__( 'Loop', 'funkycommerce-headless' ), checked: a.loop, onChange: field( 'loop' ) } ),
+ el( ToggleControl, { label: i18n.__( 'Muted', 'funkycommerce-headless' ), checked: a.muted, onChange: field( 'muted' ) } )
+ ),
+ el( components.PanelBody, { title: i18n.__( 'Content and appearance', 'funkycommerce-headless' ) },
+ el( SelectControl, { label: i18n.__( 'View', 'funkycommerce-headless' ), value: a.variant, options: [ { label: 'Full bleed', value: 'fullbleed' }, { label: 'Atmospheric glow', value: 'glow' }, { label: 'Split media/content', value: 'split' }, { label: 'Minimal editorial', value: 'minimal' }, { label: 'Compact strip', value: 'strip' } ], onChange: field( 'variant' ) } ),
+ el( TextControl, { label: i18n.__( 'Kicker', 'funkycommerce-headless' ), value: a.kicker, onChange: field( 'kicker' ) } ),
+ el( TextControl, { label: i18n.__( 'Heading', 'funkycommerce-headless' ), value: a.title, onChange: field( 'title' ) } ),
+ el( TextareaControl, { label: i18n.__( 'Description', 'funkycommerce-headless' ), value: a.description, onChange: field( 'description' ) } ),
+ el( TextControl, { label: i18n.__( 'Primary button label', 'funkycommerce-headless' ), value: a.primaryCtaLabel, onChange: field( 'primaryCtaLabel' ) } ),
+ el( TextControl, { label: i18n.__( 'Primary button URL', 'funkycommerce-headless' ), value: a.primaryCtaHref, onChange: field( 'primaryCtaHref' ) } ),
+ el( TextControl, { label: i18n.__( 'Secondary button label', 'funkycommerce-headless' ), value: a.secondaryCtaLabel, onChange: field( 'secondaryCtaLabel' ) } ),
+ el( TextControl, { label: i18n.__( 'Secondary button URL', 'funkycommerce-headless' ), value: a.secondaryCtaHref, onChange: field( 'secondaryCtaHref' ) } ),
+ el( SelectControl, { label: i18n.__( 'Text alignment', 'funkycommerce-headless' ), value: a.align, options: [ { label: 'Left', value: 'left' }, { label: 'Center', value: 'center' }, { label: 'Right', value: 'right' } ], onChange: field( 'align' ) } ),
+ el( TextControl, { label: i18n.__( 'Minimum height', 'funkycommerce-headless' ), value: a.height, onChange: field( 'height' ) } ),
+ el( RangeControl, { label: i18n.__( 'Overlay opacity', 'funkycommerce-headless' ), value: a.overlayOpacity, min: 0, max: 90, onChange: field( 'overlayOpacity' ) } )
+ )
+ ),
+ el( serverSideRender, { block: 'funkycommerce/video-hero', attributes: a } )
+ );
+ },
+ save: function () { return null; }
+ } );
+} )( window.wp.blocks, window.wp.blockEditor, window.wp.components, window.wp.element, window.wp.i18n, window.wp.serverSideRender );
diff --git a/build/lint-php.mjs b/build/lint-php.mjs
new file mode 100644
index 0000000..aab11e9
--- /dev/null
+++ b/build/lint-php.mjs
@@ -0,0 +1,54 @@
+#!/usr/bin/env node
+/**
+ * Lightweight PHP syntax linter for the theme's own PHP files, using the
+ * php-parser npm package as a stand-in for `php -l` (not available in every
+ * environment this theme is built in).
+ */
+import { readFileSync, readdirSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import path from "node:path";
+import { Engine } from "php-parser";
+
+const themeRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
+
+function phpFiles(directory, prefix = "") {
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
+ const relativePath = path.join(prefix, entry.name);
+ const absolutePath = path.join(directory, entry.name);
+ if (entry.isDirectory()) {
+ return phpFiles(absolutePath, relativePath);
+ }
+ return entry.isFile() && entry.name.endsWith(".php") ? [relativePath] : [];
+ });
+}
+
+const FILES_TO_LINT = [
+ "functions.php",
+ ...phpFiles(path.join(themeRoot, "inc"), "inc"),
+];
+
+const parser = new Engine({
+ parser: { extractDoc: true, suppressErrors: false },
+ ast: { withPositions: true },
+});
+
+let hasErrors = false;
+
+for (const relativePath of FILES_TO_LINT) {
+ const filePath = path.join(themeRoot, relativePath);
+ try {
+ const source = readFileSync(filePath, "utf8");
+ parser.parseCode(source, filePath);
+ console.log(`[lint:php] OK ${relativePath}`);
+ } catch (error) {
+ hasErrors = true;
+ console.error(`[lint:php] FAIL ${relativePath}`);
+ console.error(` ${error.message}`);
+ }
+}
+
+if (hasErrors) {
+ process.exitCode = 1;
+} else {
+ console.log(`[lint:php] All ${FILES_TO_LINT.length} file(s) parsed without syntax errors.`);
+}
diff --git a/build/sync-template-assets.mjs b/build/sync-template-assets.mjs
new file mode 100644
index 0000000..a554596
--- /dev/null
+++ b/build/sync-template-assets.mjs
@@ -0,0 +1,80 @@
+#!/usr/bin/env node
+/**
+ * Splices the compiled assets/dist/theme.css and assets/dist/theme.js files
+ * into parts/header.html and parts/footer.html between marker comments, so
+ * the native WordPress shell renders fully styled/interactive even before
+ * (or without) the optional inc/frontend-theme.php enqueue is wired up by
+ * the parent theme's functions.php.
+ *
+ * Run automatically as part of `npm run build` (see package.json), after
+ * build:css and build:js have produced assets/dist/theme.css and
+ * assets/dist/theme.js.
+ */
+import { readFileSync, writeFileSync, existsSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import path from "node:path";
+
+const themeRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
+
+const CSS_PATH = path.join(themeRoot, "assets/dist/theme.css");
+const JS_PATH = path.join(themeRoot, "assets/dist/theme.js");
+const HEADER_PATH = path.join(themeRoot, "parts/header.html");
+const FOOTER_PATH = path.join(themeRoot, "parts/footer.html");
+
+/**
+ * @param {string} filePath
+ * @param {string} startMarker
+ * @param {string} endMarker
+ * @param {string} injected
+ */
+function spliceBetweenMarkers(filePath, startMarker, endMarker, injected) {
+ const original = readFileSync(filePath, "utf8");
+ const startIndex = original.indexOf(startMarker);
+ const endIndex = original.indexOf(endMarker);
+
+ if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
+ throw new Error(
+ `Could not find markers "${startMarker}" / "${endMarker}" in ${filePath}`
+ );
+ }
+
+ const before = original.slice(0, startIndex + startMarker.length);
+ const after = original.slice(endIndex);
+ const next = `${before}\n${injected}\n${after}`;
+
+ writeFileSync(filePath, next, "utf8");
+}
+
+function run() {
+ if (!existsSync(CSS_PATH)) {
+ console.warn(
+ `[sync-template-assets] Skipping CSS inline: ${CSS_PATH} not found. Run "npm run build:css" first.`
+ );
+ } else {
+ const css = readFileSync(CSS_PATH, "utf8").trim();
+ spliceBetweenMarkers(
+ HEADER_PATH,
+ "",
+ "",
+ ``
+ );
+ console.log(`[sync-template-assets] Inlined theme.css into ${path.relative(themeRoot, HEADER_PATH)}`);
+ }
+
+ if (!existsSync(JS_PATH)) {
+ console.warn(
+ `[sync-template-assets] Skipping JS inline: ${JS_PATH} not found. Run "npm run build:js" first.`
+ );
+ } else {
+ const js = readFileSync(JS_PATH, "utf8").trim();
+ spliceBetweenMarkers(
+ FOOTER_PATH,
+ "",
+ "",
+ ``
+ );
+ console.log(`[sync-template-assets] Inlined theme.js into ${path.relative(themeRoot, FOOTER_PATH)}`);
+ }
+}
+
+run();
diff --git a/functions.php b/functions.php
index fca2965..69705c9 100644
--- a/functions.php
+++ b/functions.php
@@ -9,7 +9,7 @@
exit;
}
-define( 'FUNKYCOMMERCE_HEADLESS_VERSION', '0.7.2' );
+define( 'FUNKYCOMMERCE_HEADLESS_VERSION', '1.2.6' );
/**
* Whether Superfunky Pro is active and licensed.
@@ -66,6 +66,11 @@ function funkycommerce_has_woocommerce_graphql() {
return funkycommerce_has_woocommerce() && defined( 'WPGRAPHQL_WOOCOMMERCE_VERSION' );
}
+function funkycommerce_is_headless_mode() {
+ $settings = (array) get_option( 'funkycommerce_control_center', array() );
+ return (bool) apply_filters( 'funkycommerce_is_headless_mode', 'no' !== ( $settings['headless_mode'] ?? 'yes' ) );
+}
+
/**
* Return a safe base currency when WooCommerce is optional or inactive.
*/
@@ -77,7 +82,7 @@ function funkycommerce_base_currency() {
* Return currency names without requiring WooCommerce.
*/
function funkycommerce_currency_names() {
- return function_exists( 'get_woocommerce_currencies' )
+ $currencies = function_exists( 'get_woocommerce_currencies' )
? get_woocommerce_currencies()
: array(
'EUR' => __( 'Euro', 'funkycommerce-headless' ),
@@ -85,13 +90,18 @@ function funkycommerce_currency_names() {
'GBP' => __( 'Pound sterling', 'funkycommerce-headless' ),
'PLN' => __( 'Polish złoty', 'funkycommerce-headless' ),
);
+
+ $currencies['BTC'] = $currencies['BTC'] ?? __( 'Bitcoin', 'funkycommerce-headless' );
+ $currencies['ETH'] = $currencies['ETH'] ?? __( 'Ethereum', 'funkycommerce-headless' );
+
+ return $currencies;
}
/**
* Return currency symbols without requiring WooCommerce.
*/
function funkycommerce_currency_symbols() {
- return function_exists( 'get_woocommerce_currency_symbols' )
+ $symbols = function_exists( 'get_woocommerce_currency_symbols' )
? get_woocommerce_currency_symbols()
: array(
'EUR' => '€',
@@ -99,6 +109,11 @@ function funkycommerce_currency_symbols() {
'GBP' => '£',
'PLN' => 'zł',
);
+
+ $symbols['BTC'] = '₿';
+ $symbols['ETH'] = 'Ξ';
+
+ return $symbols;
}
/**
@@ -117,18 +132,54 @@ function funkycommerce_frontend_url( $path = '' ) {
}
require_once get_template_directory() . '/inc/headless-login.php';
+require_once get_template_directory() . '/inc/notifications.php';
+require_once get_template_directory() . '/inc/checkout-context.php';
require_once get_template_directory() . '/inc/community.php';
+require_once get_template_directory() . '/inc/avatar.php';
+require_once get_template_directory() . '/inc/registration-email-verification.php';
require_once get_template_directory() . '/inc/account.php';
require_once get_template_directory() . '/inc/navigation-commerce.php';
+require_once get_template_directory() . '/inc/recent-orders.php';
+require_once get_template_directory() . '/inc/web-push.php';
require_once get_template_directory() . '/inc/crypto-payments.php';
require_once get_template_directory() . '/inc/multilingual-content.php';
+require_once get_template_directory() . '/inc/ratings.php';
+require_once get_template_directory() . '/inc/saved-lists.php';
require_once get_template_directory() . '/inc/control-center-schema.php';
+require_once get_template_directory() . '/inc/artifact-protocol.php';
+require_once get_template_directory() . '/inc/artifact-store.php';
+require_once get_template_directory() . '/inc/artifact-rest.php';
+require_once get_template_directory() . '/inc/superfunky-licence-client.php';
+require_once get_template_directory() . '/inc/superfunky-update-client.php';
+Superfunky_Licence_Client::register();
+Superfunky_Update_Client::register_product(
+ array(
+ 'access' => 'public',
+ 'file' => null,
+ 'name' => 'Superfunky Headless',
+ 'product_id' => 'funkycommerce-headless',
+ 'requires_php' => '7.4',
+ 'slug' => 'funkycommerce-headless',
+ 'type' => 'theme',
+ 'url' => 'https://github.com/coded-letter/superfunky-theme',
+ 'version' => FUNKYCOMMERCE_HEADLESS_VERSION,
+ )
+);
+require_once get_template_directory() . '/inc/admin-theme.php';
require_once get_template_directory() . '/inc/woocommerce-admin.php';
require_once get_template_directory() . '/inc/submissions.php';
require_once get_template_directory() . '/inc/control-center.php';
+require_once get_template_directory() . '/inc/admin-view-links.php';
+require_once get_template_directory() . '/inc/frontend-theme.php';
+require_once get_template_directory() . '/inc/custom-404.php';
+require_once get_template_directory() . '/inc/native-woocommerce.php';
+require_once get_template_directory() . '/inc/native-shortcodes.php';
require_once get_template_directory() . '/inc/build-webhooks.php';
+require_once get_template_directory() . '/inc/artifact-renderer.php';
+require_once get_template_directory() . '/inc/artifact-invalidation.php';
require_once get_template_directory() . '/inc/security-hardening.php';
require_once get_template_directory() . '/inc/seo-feeds.php';
+require_once get_template_directory() . '/inc/backend-preview.php';
/**
* Configure the block editor and register menu locations consumed by the storefront.
@@ -163,6 +214,10 @@ function funkycommerce_headless_setup() {
*/
function funkycommerce_headless_component_shortcodes() {
return array(
+ 'funkycommerce_shop',
+ 'funkycommerce_blog',
+ 'product_archive',
+ 'post_archive',
'woocommerce_cart',
'woocommerce_checkout',
'woocommerce_my_account',
@@ -172,6 +227,9 @@ function funkycommerce_headless_component_shortcodes() {
'cart',
'checkout',
'account',
+ 'wishlist',
+ 'reading_list',
+ 'auth',
'funkycommerce_wishlist',
'funkycommerce_reading_list',
'funkycommerce_auth',
@@ -184,11 +242,17 @@ function funkycommerce_headless_component_shortcodes() {
function funkycommerce_headless_content_shortcodes() {
return array(
'hero',
+ 'video-hero',
+ 'spotify-radio',
+ 'chat_assistant',
'categories',
'slider',
'carousel',
'grid',
+ 'sticky-posts',
+ 'sticky_posts',
'tags',
+ 'product-tags',
'authors',
'reviews',
'comments',
@@ -201,6 +265,10 @@ function funkycommerce_headless_content_shortcodes() {
'related-sections',
'order-success',
'unsubscribe-form',
+ 'funkycommerce_map',
+ 'funkycommerce_locations',
+ 'gml_map',
+ 'sorted_locations',
);
}
@@ -224,6 +292,10 @@ function funkycommerce_headless_component_blocks() {
function funkycommerce_render_headless_component_marker( $attributes, $content, $tag ) {
$schemas = funkycommerce_component_shortcode_schemas();
$schema_key = str_replace( 'woocommerce_', '', str_replace( 'funkycommerce_', '', $tag ) );
+ $schema_key = array(
+ 'shop' => 'product_archive',
+ 'blog' => 'post_archive',
+ )[ $schema_key ] ?? $schema_key;
$schema = isset( $schemas[ $schema_key ] ) ? $schemas[ $schema_key ] : array();
$defaults = array_map(
static function ( $definition ) {
@@ -244,6 +316,8 @@ static function ( $definition ) {
function funkycommerce_component_shortcode_schemas() {
return array(
+ 'product_archive' => array(),
+ 'post_archive' => array(),
'cart' => array(
'layout' => array( 'default' => 'classic', 'enum' => array( 'classic', 'editorial' ) ),
'summary_position' => array( 'default' => 'sticky', 'enum' => array( 'sticky', 'static' ) ),
@@ -267,16 +341,21 @@ function funkycommerce_component_shortcode_schemas() {
'layout' => array( 'default' => 'cards', 'enum' => array( 'cards', 'editorial-2col' ) ),
),
'account' => array(
- 'default_tab' => array( 'default' => 'dashboard', 'enum' => array( 'dashboard', 'orders', 'addresses', 'community' ) ),
- 'tabs' => array( 'default' => 'dashboard,orders,addresses,community', 'type' => 'account-tab-list' ),
+ 'default_tab' => array( 'default' => 'dashboard', 'enum' => array( 'dashboard', 'orders', 'downloads', 'addresses', 'community' ) ),
+ 'tabs' => array( 'default' => 'dashboard,orders,downloads,addresses,community', 'type' => 'account-tab-list' ),
),
'auth' => array(
- 'mode' => array( 'default' => 'login', 'enum' => array( 'login', 'register', 'forgot-password' ) ),
- 'layout' => array( 'default' => 'split', 'enum' => array( 'split', 'centered', 'image-bg' ) ),
+ 'mode' => array( 'default' => 'login', 'enum' => array( 'login', 'register', 'forgot-password', 'combined' ) ),
+ 'default_mode' => array( 'default' => 'login', 'enum' => array( 'login', 'register', 'forgot-password' ) ),
+ 'layout' => array( 'default' => 'split', 'enum' => array( 'split', 'centered', 'image-bg' ) ),
),
);
}
+add_shortcode( 'funkycommerce_shop', 'funkycommerce_render_headless_component_marker' );
+add_shortcode( 'product_archive', 'funkycommerce_render_headless_component_marker' );
+add_shortcode( 'funkycommerce_blog', 'funkycommerce_render_headless_component_marker' );
+add_shortcode( 'post_archive', 'funkycommerce_render_headless_component_marker' );
add_shortcode( 'funkycommerce_cart', 'funkycommerce_render_headless_component_marker' );
add_shortcode( 'cart', 'funkycommerce_render_headless_component_marker' );
add_shortcode( 'funkycommerce_checkout', 'funkycommerce_render_headless_component_marker' );
@@ -284,31 +363,99 @@ function funkycommerce_component_shortcode_schemas() {
add_shortcode( 'funkycommerce_account', 'funkycommerce_render_headless_component_marker' );
add_shortcode( 'account', 'funkycommerce_render_headless_component_marker' );
add_shortcode( 'funkycommerce_wishlist', 'funkycommerce_render_headless_component_marker' );
+add_shortcode( 'wishlist', 'funkycommerce_render_headless_component_marker' );
add_shortcode( 'funkycommerce_reading_list', 'funkycommerce_render_headless_component_marker' );
+add_shortcode( 'reading_list', 'funkycommerce_render_headless_component_marker' );
add_shortcode( 'funkycommerce_auth', 'funkycommerce_render_headless_component_marker' );
+add_shortcode( 'auth', 'funkycommerce_render_headless_component_marker' );
+
+/**
+ * Shared collection offset schema definition for content shortcodes.
+ */
+function funkycommerce_collection_shortcode_offset_definition() {
+ return array(
+ 'default' => 0,
+ 'type' => 'integer',
+ 'min' => 0,
+ 'max' => 1000000,
+ );
+}
/**
* Return the backend contract for editor-authored storefront content modules.
*/
function funkycommerce_content_shortcode_schemas() {
+ // Shared by both the canonical `sticky-posts` tag and its neutral `sticky_posts`
+ // alias (kept as one definition, unlike the historically hand-duplicated
+ // funkycommerce_map/gml_map pair, so the two names can never drift apart).
+ $sticky_posts_schema = array(
+ 'layout' => array( 'default' => 'grid', 'enum' => array( 'grid', 'carousel', 'compact-list' ) ),
+ 'card_variant' => array( 'default' => 'default', 'enum' => array( 'default', 'compact', 'editorial', 'minimal' ) ),
+ 'columns' => array( 'default' => 3, 'type' => 'integer', 'min' => 1, 'max' => 4 ),
+ 'limit' => array( 'default' => 6, 'type' => 'integer', 'min' => 1, 'max' => 24 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
+ 'autoplay' => array( 'default' => 4000, 'type' => 'integer', 'min' => 0, 'max' => 60000 ),
+ 'loop' => array( 'default' => 'true', 'type' => 'boolean' ),
+ 'title' => array( 'default' => __( 'Pinned posts', 'funkycommerce-headless' ) ),
+ 'subtitle' => array( 'default' => '' ),
+ );
+
return array(
'hero' => array(
'variant' => array( 'default' => 'fullbleed', 'enum' => array( 'glow', 'fullbleed', 'split', 'minimal', 'strip' ) ),
'kicker' => array( 'default' => '' ),
'title' => array( 'default' => __( 'Storefront hero', 'funkycommerce-headless' ) ),
+ 'h2' => array( 'default' => '' ),
+ 'heading_level' => array( 'default' => 'h1', 'enum' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ),
'description' => array( 'default' => '' ),
'image' => array( 'default' => '', 'type' => 'url' ),
'primary_cta_label' => array( 'default' => '' ),
'primary_cta_href' => array( 'default' => '', 'type' => 'url-path' ),
+ 'primary_cta_target' => array( 'default' => '_self', 'enum' => array( '_self', '_blank' ) ),
+ 'primary_cta_rel' => array( 'default' => '' ),
'secondary_cta_label' => array( 'default' => '' ),
'secondary_cta_href' => array( 'default' => '', 'type' => 'url-path' ),
+ 'secondary_cta_target' => array( 'default' => '_self', 'enum' => array( '_self', '_blank' ) ),
+ 'secondary_cta_rel' => array( 'default' => '' ),
'fullwidth' => array( 'default' => 'false', 'type' => 'boolean' ),
'height' => array( 'default' => '' ),
),
+ 'video-hero' => array(
+ 'variant' => array( 'default' => 'fullbleed', 'enum' => array( 'glow', 'fullbleed', 'split', 'minimal', 'strip' ) ),
+ 'src' => array( 'default' => '', 'type' => 'url' ),
+ 'poster' => array( 'default' => '', 'type' => 'url' ),
+ 'kicker' => array( 'default' => '' ),
+ 'title' => array( 'default' => __( 'Video hero', 'funkycommerce-headless' ) ),
+ 'description' => array( 'default' => '' ),
+ 'primary_cta_label' => array( 'default' => '' ),
+ 'primary_cta_href' => array( 'default' => '', 'type' => 'url-path' ),
+ 'primary_cta_target' => array( 'default' => '_self', 'enum' => array( '_self', '_blank' ) ),
+ 'primary_cta_rel' => array( 'default' => '' ),
+ 'secondary_cta_label' => array( 'default' => '' ),
+ 'secondary_cta_href' => array( 'default' => '', 'type' => 'url-path' ),
+ 'secondary_cta_target' => array( 'default' => '_self', 'enum' => array( '_self', '_blank' ) ),
+ 'secondary_cta_rel' => array( 'default' => '' ),
+ 'align' => array( 'default' => 'left', 'enum' => array( 'left', 'center', 'right' ) ),
+ 'height' => array( 'default' => '70vh' ),
+ 'overlay_opacity' => array( 'default' => 55, 'type' => 'integer', 'min' => 0, 'max' => 90 ),
+ 'autoplay' => array( 'default' => 'true', 'type' => 'boolean' ),
+ 'loop' => array( 'default' => 'true', 'type' => 'boolean' ),
+ 'muted' => array( 'default' => 'true', 'type' => 'boolean' ),
+ ),
+ 'spotify-radio' => array(
+ 'uri' => array( 'default' => 'https://open.spotify.com/playlist/37i9dQZF1DWWQRwui0ExPn' ),
+ 'content_type' => array( 'default' => 'playlist', 'enum' => array( 'track', 'album', 'playlist', 'artist', 'show', 'episode' ) ),
+ 'height' => array( 'default' => 400, 'type' => 'integer', 'min' => 152, 'max' => 800 ),
+ 'theme' => array( 'default' => 'auto', 'enum' => array( 'auto', 'dark', 'light' ) ),
+ 'title' => array( 'default' => __( 'Superfunky Radio', 'funkycommerce-headless' ) ),
+ 'description' => array( 'default' => '' ),
+ ),
+ 'chat_assistant' => array(),
'categories' => array(
'type' => array( 'default' => 'product', 'enum' => array( 'product', 'post' ) ),
'layout' => array( 'default' => 'cards', 'enum' => array( 'cards', 'compact', 'minimal', 'editorial', 'graphical', 'pills' ) ),
'columns' => array( 'default' => 3, 'type' => 'integer', 'min' => 2, 'max' => 4 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 3, 'type' => 'integer', 'min' => 1, 'max' => 24 ),
'include' => array( 'default' => '' ),
'orderby' => array( 'default' => 'name', 'enum' => array( 'name', 'count', 'include' ) ),
@@ -316,10 +463,11 @@ function funkycommerce_content_shortcode_schemas() {
'title' => array( 'default' => '' ),
),
'slider' => array(
- 'type' => array( 'default' => 'product', 'enum' => array( 'campaign', 'product', 'post' ) ),
+ 'type' => array( 'default' => 'product', 'enum' => array( 'campaign', 'cinematic', 'product', 'post' ) ),
'layout' => array( 'default' => '3/3', 'enum' => array( '3/3', '2/3', '1/3' ) ),
'card_variant' => array( 'default' => 'default', 'enum' => array( 'default', 'compact', 'editorial', 'minimal', 'gallery', 'simple', 'variation', 'expandable' ) ),
'slides' => array( 'default' => 3, 'type' => 'integer', 'min' => 1, 'max' => 12 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 6, 'type' => 'integer', 'min' => 1, 'max' => 48 ),
'navigation' => array( 'default' => 'both', 'enum' => array( 'dots', 'arrows', 'both', 'none' ) ),
'autoplay' => array( 'default' => 5000, 'type' => 'integer', 'min' => 0, 'max' => 60000 ),
@@ -335,13 +483,28 @@ function funkycommerce_content_shortcode_schemas() {
'order' => array( 'default' => 'desc', 'enum' => array( 'asc', 'desc' ) ),
'title' => array( 'default' => '' ),
'subtitle' => array( 'default' => '' ),
+ 'section_heading_level' => array( 'default' => 'h3', 'enum' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ),
+ 'heading_level' => array( 'default' => 'h2', 'enum' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ),
+ 'first_heading_level' => array( 'default' => '', 'enum' => array( '', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ),
'kicker' => array( 'default' => '' ),
'description' => array( 'default' => '' ),
'image' => array( 'default' => '', 'type' => 'url' ),
+ 'bgimgs' => array( 'default' => '' ),
+ 'h1' => array( 'default' => '' ),
+ 'p' => array( 'default' => '' ),
+ 'pill' => array( 'default' => '' ),
'titles' => array( 'default' => '' ),
'descriptions' => array( 'default' => '' ),
'images' => array( 'default' => '', 'type' => 'url-list' ),
'kickers' => array( 'default' => '' ),
+ 'primary_cta_label' => array( 'default' => '' ),
+ 'primary_cta_href' => array( 'default' => '', 'type' => 'url-path' ),
+ 'primary_cta_target' => array( 'default' => '_self', 'enum' => array( '_self', '_blank' ) ),
+ 'primary_cta_rel' => array( 'default' => '' ),
+ 'secondary_cta_label' => array( 'default' => '' ),
+ 'secondary_cta_href' => array( 'default' => '', 'type' => 'url-path' ),
+ 'secondary_cta_target' => array( 'default' => '_self', 'enum' => array( '_self', '_blank' ) ),
+ 'secondary_cta_rel' => array( 'default' => '' ),
'fullwidth' => array( 'default' => 'false', 'type' => 'boolean' ),
'height' => array( 'default' => '' ),
),
@@ -349,6 +512,7 @@ function funkycommerce_content_shortcode_schemas() {
'type' => array( 'default' => 'product', 'enum' => array( 'product', 'post' ) ),
'card_variant' => array( 'default' => 'default', 'enum' => array( 'default', 'compact', 'editorial', 'minimal', 'gallery', 'simple', 'variation', 'expandable' ) ),
'columns' => array( 'default' => 4, 'type' => 'integer', 'min' => 1, 'max' => 6 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 48 ),
'include' => array( 'default' => '' ),
'category' => array( 'default' => '' ),
@@ -361,6 +525,7 @@ function funkycommerce_content_shortcode_schemas() {
'loop' => array( 'default' => 'true', 'type' => 'boolean' ),
'title' => array( 'default' => '' ),
'subtitle' => array( 'default' => '' ),
+ 'section_heading_level' => array( 'default' => 'h3', 'enum' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ),
),
'grid' => array(
'type' => array( 'default' => 'product', 'enum' => array( 'product', 'post', 'community-article' ) ),
@@ -368,6 +533,7 @@ function funkycommerce_content_shortcode_schemas() {
'layout' => array( 'default' => 'standard', 'enum' => array( 'standard', 'compact', 'editorial', 'masonry' ) ),
'columns' => array( 'default' => 3, 'type' => 'integer', 'min' => 1, 'max' => 6 ),
'page_size' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 48 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'paginated' => array( 'default' => 'true', 'type' => 'boolean' ),
'include' => array( 'default' => '' ),
'category' => array( 'default' => '' ),
@@ -381,16 +547,29 @@ function funkycommerce_content_shortcode_schemas() {
'title' => array( 'default' => '' ),
'subtitle' => array( 'default' => '' ),
),
+ 'sticky-posts' => $sticky_posts_schema,
+ 'sticky_posts' => $sticky_posts_schema,
'tags' => array(
'layout' => array( 'default' => 'pills', 'enum' => array( 'pills', 'cards', 'compact' ) ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 24, 'type' => 'integer', 'min' => 1, 'max' => 100 ),
'include' => array( 'default' => '' ),
'orderby' => array( 'default' => 'name', 'enum' => array( 'name', 'count', 'include' ) ),
'order' => array( 'default' => 'asc', 'enum' => array( 'asc', 'desc' ) ),
'title' => array( 'default' => __( 'Tags', 'funkycommerce-headless' ) ),
),
+ 'product-tags' => array(
+ 'layout' => array( 'default' => 'pills', 'enum' => array( 'pills', 'cards', 'compact' ) ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
+ 'limit' => array( 'default' => 24, 'type' => 'integer', 'min' => 1, 'max' => 100 ),
+ 'include' => array( 'default' => '' ),
+ 'orderby' => array( 'default' => 'name', 'enum' => array( 'name', 'count', 'include' ) ),
+ 'order' => array( 'default' => 'asc', 'enum' => array( 'asc', 'desc' ) ),
+ 'title' => array( 'default' => __( 'Product tags', 'funkycommerce-headless' ) ),
+ ),
'authors' => array(
'layout' => array( 'default' => 'cards', 'enum' => array( 'cards', 'compact' ) ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 100 ),
'include' => array( 'default' => '' ),
'show_bio' => array( 'default' => 'true', 'type' => 'boolean' ),
@@ -402,6 +581,7 @@ function funkycommerce_content_shortcode_schemas() {
'reviews' => array(
'layout' => array( 'default' => 'grid-4', 'enum' => array( 'grid-4', 'grid-3', 'grid-5', 'masonry', 'compact' ) ),
'variant' => array( 'default' => 'cards', 'enum' => array( 'cards', 'full', 'compact' ) ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 48 ),
'product' => array( 'default' => '' ),
'min_rating' => array( 'default' => 0, 'type' => 'number', 'min' => 0, 'max' => 5 ),
@@ -413,6 +593,7 @@ function funkycommerce_content_shortcode_schemas() {
'comments' => array(
'layout' => array( 'default' => 'cards', 'enum' => array( 'cards', 'compact' ) ),
'variant' => array( 'default' => 'cards', 'enum' => array( 'cards', 'full', 'compact' ) ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 48 ),
'post' => array( 'default' => '' ),
'min_rating' => array( 'default' => 0, 'type' => 'number', 'min' => 0, 'max' => 5 ),
@@ -425,6 +606,7 @@ function funkycommerce_content_shortcode_schemas() {
'layout' => array( 'default' => 'masonry', 'enum' => array( 'masonry', 'grid-3', 'grid-4', 'list', 'compact' ) ),
'load_mode' => array( 'default' => 'manual', 'enum' => array( 'manual', 'infinite' ) ),
'page_size' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 48 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'show_filters' => array( 'default' => 'true', 'type' => 'boolean' ),
'tags' => array( 'default' => '' ),
'author' => array( 'default' => '' ),
@@ -438,6 +620,7 @@ function funkycommerce_content_shortcode_schemas() {
'layout' => array( 'default' => 'gradient', 'enum' => array( 'gradient', 'split', 'image-bg' ) ),
'kicker' => array( 'default' => __( 'Community', 'funkycommerce-headless' ) ),
'title' => array( 'default' => __( 'See how the community styles it', 'funkycommerce-headless' ) ),
+ 'heading_level' => array( 'default' => 'h1', 'enum' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ),
'description' => array( 'default' => '' ),
'image' => array( 'default' => '', 'type' => 'url' ),
'show_upload' => array( 'default' => 'true', 'type' => 'boolean' ),
@@ -446,6 +629,7 @@ function funkycommerce_content_shortcode_schemas() {
'layout' => array( 'default' => 'grid', 'enum' => array( 'grid', 'compact', 'carousel' ) ),
'card_variant' => array( 'default' => 'default', 'enum' => array( 'default', 'minimal', 'editorial', 'gallery', 'simple', 'variation', 'expandable' ) ),
'columns' => array( 'default' => 4, 'type' => 'integer', 'min' => 1, 'max' => 6 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 48 ),
'min_rating' => array( 'default' => 0, 'type' => 'number', 'min' => 0, 'max' => 5 ),
'title' => array( 'default' => __( 'Shop the community', 'funkycommerce-headless' ) ),
@@ -455,6 +639,7 @@ function funkycommerce_content_shortcode_schemas() {
'tags' => array( 'default' => '' ),
'tag_limit' => array( 'default' => 3, 'type' => 'integer', 'min' => 1, 'max' => 12 ),
'post_limit' => array( 'default' => 3, 'type' => 'integer', 'min' => 1, 'max' => 12 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'min_likes' => array( 'default' => 0, 'type' => 'integer', 'min' => 0, 'max' => 1000000 ),
'date_from' => array( 'default' => '', 'type' => 'date' ),
'date_to' => array( 'default' => '', 'type' => 'date' ),
@@ -463,14 +648,18 @@ function funkycommerce_content_shortcode_schemas() {
'community-members' => array(
'layout' => array( 'default' => 'grid', 'enum' => array( 'grid', 'compact', 'list' ) ),
'columns' => array( 'default' => 6, 'type' => 'integer', 'min' => 1, 'max' => 6 ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 12, 'type' => 'integer', 'min' => 1, 'max' => 100 ),
'include' => array( 'default' => '' ),
- 'role' => array( 'default' => 'all', 'enum' => array( 'all', 'member', 'creator', 'collaborator' ) ),
+ 'members' => array( 'default' => '', 'type' => 'community-role-list' ),
+ 'role' => array( 'default' => 'all', 'type' => 'community-role-list' ),
+ 'permission' => array( 'default' => 'all', 'type' => 'community-role-list' ),
'show_bio' => array( 'default' => 'false', 'type' => 'boolean' ),
'title' => array( 'default' => __( 'Members to follow', 'funkycommerce-headless' ) ),
),
'testimonials' => array(
'layout' => array( 'default' => 'grid-3', 'enum' => array( 'grid-3', 'carousel', 'compact' ) ),
+ 'offset' => funkycommerce_collection_shortcode_offset_definition(),
'limit' => array( 'default' => 3, 'type' => 'integer', 'min' => 1, 'max' => 12 ),
'min_rating' => array( 'default' => 4, 'type' => 'number', 'min' => 0, 'max' => 5 ),
'date_from' => array( 'default' => '', 'type' => 'date' ),
@@ -492,9 +681,64 @@ function funkycommerce_content_shortcode_schemas() {
'title' => array( 'default' => __( 'We’re sorry to see you go.', 'funkycommerce-headless' ) ),
'description' => array( 'default' => __( 'Confirm your email address and tell us why you’re unsubscribing.', 'funkycommerce-headless' ) ),
),
+ 'funkycommerce_map' => array(
+ 'height' => array( 'default' => 500, 'type' => 'integer', 'min' => 240, 'max' => 1000 ),
+ ),
+ 'funkycommerce_locations' => array(),
+ 'gml_map' => array(
+ 'height' => array( 'default' => 500, 'type' => 'integer', 'min' => 240, 'max' => 1000 ),
+ ),
+ 'sorted_locations' => array(),
);
}
+/**
+ * Public filter key for a registered WordPress role.
+ */
+function funkycommerce_community_role_type( $role ) {
+ $role = sanitize_key( (string) $role );
+ if ( 'administrator' === $role ) {
+ return 'admin';
+ }
+ return $role;
+}
+
+/**
+ * Resolve role slugs and human-readable role labels accepted by the shortcode.
+ */
+function funkycommerce_community_role_filter_aliases() {
+ $aliases = array(
+ 'all' => 'all',
+ 'admin' => 'admin',
+ 'administrator' => 'admin',
+ 'member' => 'member',
+ 'creator' => 'creator',
+ 'collaborator' => 'collaborator',
+ 'customer' => 'customer',
+ 'subscriber' => 'subscriber',
+ 'editor' => 'editor',
+ 'author' => 'author',
+ 'contributor' => 'contributor',
+ 'shop-manager' => 'shop_manager',
+ 'seo-editor' => 'wpseo_editor',
+ 'seo-manager' => 'wpseo_manager',
+ );
+ $roles = function_exists( 'wp_roles' ) ? wp_roles()->roles : array();
+ foreach ( $roles as $slug => $details ) {
+ $type = funkycommerce_community_role_type( $slug );
+ if ( '' === $type ) {
+ continue;
+ }
+ $aliases[ sanitize_key( (string) $slug ) ] = $type;
+ $aliases[ sanitize_title( str_replace( '_', ' ', (string) $slug ) ) ] = $type;
+ $label = sanitize_title( (string) ( $details['name'] ?? '' ) );
+ if ( $label ) {
+ $aliases[ $label ] = $type;
+ }
+ }
+ return $aliases;
+}
+
/**
* Normalize one shortcode attribute according to its schema definition.
*/
@@ -540,13 +784,30 @@ static function ( $item ) {
array_filter(
array_map( 'sanitize_key', explode( ',', (string) $value ) ),
static function ( $item ) {
- return in_array( $item, array( 'dashboard', 'orders', 'addresses', 'community' ), true );
+ return in_array( $item, array( 'dashboard', 'orders', 'downloads', 'addresses', 'community' ), true );
}
)
)
);
return $items ? implode( ',', $items ) : $definition['default'];
}
+ if ( 'community-role-list' === $type ) {
+ $aliases = funkycommerce_community_role_filter_aliases();
+ $items = array_values(
+ array_unique(
+ array_filter(
+ array_map(
+ static function ( $item ) use ( $aliases ) {
+ $key = sanitize_title( trim( (string) $item ) );
+ return $aliases[ $key ] ?? '';
+ },
+ explode( ',', (string) $value )
+ )
+ )
+ )
+ );
+ return in_array( 'all', $items, true ) ? 'all' : implode( ',', $items );
+ }
if ( 'url-path' === $type ) {
$value = trim( (string) $value );
return 0 === strpos( $value, '/' ) ? sanitize_text_field( $value ) : esc_url_raw( $value );
@@ -557,12 +818,54 @@ static function ( $item ) {
/**
* Render a validated marker that the React storefront replaces with a live module.
*/
+function funkycommerce_apply_content_shortcode_aliases( $attributes, $tag ) {
+ $aliases = array();
+ if ( 'hero' === $tag ) {
+ if ( ! empty( $attributes['h2'] ) && empty( $attributes['h1'] ) ) {
+ $attributes['heading_level'] = 'h2';
+ $attributes['title'] = $attributes['h2'];
+ }
+ $aliases = array(
+ 'pill' => 'kicker',
+ 'h1' => 'title',
+ 'p' => 'description',
+ 'bgimg' => 'image',
+ );
+ } elseif ( 'slider' === $tag ) {
+ $aliases = array(
+ 'h1' => 'titles',
+ 'p' => 'descriptions',
+ 'bgimgs' => 'images',
+ 'pill' => 'kickers',
+ );
+ }
+ foreach ( $aliases as $alias => $canonical ) {
+ if ( isset( $attributes[ $alias ] ) && '' !== trim( (string) $attributes[ $alias ] ) ) {
+ $attributes[ $canonical ] = $attributes[ $alias ];
+ }
+ }
+
+ foreach ( array( 'cta1' => 'primary_cta', 'cta2' => 'secondary_cta' ) as $alias => $prefix ) {
+ if ( empty( $attributes[ $alias ] ) ) {
+ continue;
+ }
+ $parts = array_map( 'trim', explode( '|', (string) $attributes[ $alias ] ) );
+ foreach ( array( 'label', 'href', 'target', 'rel' ) as $index => $field ) {
+ if ( ! empty( $parts[ $index ] ) ) {
+ $attributes[ $prefix . '_' . $field ] = $parts[ $index ];
+ }
+ }
+ }
+ return $attributes;
+}
+
function funkycommerce_render_content_shortcode_marker( $attributes, $content, $tag ) {
$schemas = funkycommerce_content_shortcode_schemas();
if ( ! isset( $schemas[ $tag ] ) ) {
return '';
}
+ $attributes = funkycommerce_apply_content_shortcode_aliases( $attributes, $tag );
$schema = $schemas[ $tag ];
$defaults = array_map(
static function ( $definition ) {
@@ -581,54 +884,87 @@ static function ( $definition ) {
return $marker . '>';
}
-foreach ( array_keys( funkycommerce_content_shortcode_schemas() ) as $funkycommerce_shortcode_tag ) {
- add_shortcode( $funkycommerce_shortcode_tag, 'funkycommerce_render_content_shortcode_marker' );
-}
-
/**
- * Legacy helper retained for upgrade safety now that shortcode-driven pages are regular Pages.
+ * Register content shortcodes after WordPress has initialized translations.
*/
-function funkycommerce_ensure_custom_special_pages() {
- return;
+function funkycommerce_register_content_shortcodes() {
+ foreach ( array_keys( funkycommerce_content_shortcode_schemas() ) as $shortcode_tag ) {
+ // The paid plugin owns native [chat_assistant] rendering. The theme only
+ // replaces it with a marker when the separate headless app is active.
+ if ( 'chat_assistant' === $shortcode_tag && ( ! function_exists( 'funkycommerce_is_headless_mode' ) || ! funkycommerce_is_headless_mode() ) ) {
+ continue;
+ }
+ add_shortcode( $shortcode_tag, 'funkycommerce_render_content_shortcode_marker' );
+ }
}
-add_action( 'after_switch_theme', 'funkycommerce_ensure_custom_special_pages' );
-add_action( 'admin_init', 'funkycommerce_ensure_custom_special_pages' );
+add_action( 'init', 'funkycommerce_register_content_shortcodes' );
/**
- * Resolve a special storefront page by its stable route slug.
+ * Register the editor-friendly dynamic counterpart to [video-hero].
*/
-function funkycommerce_get_special_page_id( $key ) {
- $page_slugs = array(
- 'home' => 'home',
- 'shop' => 'shop',
- 'blog' => 'blog',
- 'cart' => 'cart',
- 'checkout' => 'checkout',
- 'account' => 'account',
+function funkycommerce_register_video_hero_block() {
+ $script_path = get_template_directory() . '/assets/video-hero-block.js';
+ wp_register_script(
+ 'funkycommerce-video-hero-block',
+ get_template_directory_uri() . '/assets/video-hero-block.js',
+ array( 'wp-blocks', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n', 'wp-server-side-render' ),
+ file_exists( $script_path ) ? (string) filemtime( $script_path ) : null,
+ true
);
- $slug = $page_slugs[ $key ] ?? '';
- $page = $slug ? get_page_by_path( $slug, OBJECT, 'page' ) : null;
-
- // Keep the conventional WooCommerce slug working for existing installations.
- if ( ! $page && 'account' === $key ) {
- $page = get_page_by_path( 'my-account', OBJECT, 'page' );
+ register_block_type(
+ 'funkycommerce/video-hero',
+ array(
+ 'api_version' => 3,
+ 'editor_script' => 'funkycommerce-video-hero-block',
+ 'attributes' => array(
+ 'src' => array( 'type' => 'string', 'default' => '' ), 'variant' => array( 'type' => 'string', 'default' => 'fullbleed' ), 'poster' => array( 'type' => 'string', 'default' => '' ),
+ 'kicker' => array( 'type' => 'string', 'default' => '' ), 'title' => array( 'type' => 'string', 'default' => 'Video hero' ),
+ 'description' => array( 'type' => 'string', 'default' => '' ), 'primaryCtaLabel' => array( 'type' => 'string', 'default' => '' ),
+ 'primaryCtaHref' => array( 'type' => 'string', 'default' => '' ), 'secondaryCtaLabel' => array( 'type' => 'string', 'default' => '' ),
+ 'secondaryCtaHref' => array( 'type' => 'string', 'default' => '' ), 'align' => array( 'type' => 'string', 'default' => 'left' ),
+ 'height' => array( 'type' => 'string', 'default' => '70vh' ), 'overlayOpacity' => array( 'type' => 'number', 'default' => 55 ),
+ 'autoplay' => array( 'type' => 'boolean', 'default' => true ), 'loop' => array( 'type' => 'boolean', 'default' => true ),
+ 'muted' => array( 'type' => 'boolean', 'default' => true ),
+ ),
+ 'render_callback' => 'funkycommerce_render_video_hero_block',
+ )
+ );
+}
+add_action( 'init', 'funkycommerce_register_video_hero_block', 30 );
+
+function funkycommerce_render_video_hero_block( $attributes ) {
+ $map = array(
+ 'src' => 'src', 'variant' => 'variant', 'poster' => 'poster', 'kicker' => 'kicker', 'title' => 'title', 'description' => 'description',
+ 'primaryCtaLabel' => 'primary_cta_label', 'primaryCtaHref' => 'primary_cta_href',
+ 'secondaryCtaLabel' => 'secondary_cta_label', 'secondaryCtaHref' => 'secondary_cta_href',
+ 'align' => 'align', 'height' => 'height', 'overlayOpacity' => 'overlay_opacity',
+ 'autoplay' => 'autoplay', 'loop' => 'loop', 'muted' => 'muted',
+ );
+ $shortcode_attributes = array();
+ foreach ( $map as $block_name => $shortcode_name ) {
+ if ( array_key_exists( $block_name, $attributes ) ) {
+ $value = is_bool( $attributes[ $block_name ] ) ? ( $attributes[ $block_name ] ? 'true' : 'false' ) : $attributes[ $block_name ];
+ $shortcode_attributes[] = $shortcode_name . '="' . esc_attr( $value ) . '"';
+ }
}
-
- return $page ? (int) $page->ID : 0;
+ return do_shortcode( '[video-hero ' . implode( ' ', $shortcode_attributes ) . ']' );
}
/**
- * Extract a database ID from a WPGraphQL Page source.
+ * Extract a database ID from a WPGraphQL content-node source.
*/
-function funkycommerce_graphql_page_database_id( $page ) {
- if ( $page instanceof WP_Post ) {
- return (int) $page->ID;
+function funkycommerce_graphql_content_database_id( $node ) {
+ if ( $node instanceof WP_Post ) {
+ return (int) $node->ID;
}
- if ( is_object( $page ) && isset( $page->databaseId ) ) {
- return (int) $page->databaseId;
+ if ( is_object( $node ) && is_callable( array( $node, 'get_id' ) ) ) {
+ return (int) $node->get_id();
}
- if ( is_object( $page ) && isset( $page->ID ) ) {
- return (int) $page->ID;
+ if ( is_object( $node ) && isset( $node->databaseId ) ) {
+ return (int) $node->databaseId;
+ }
+ if ( is_object( $node ) && isset( $node->ID ) ) {
+ return (int) $node->ID;
}
return 0;
}
@@ -695,15 +1031,164 @@ function funkycommerce_extract_headless_references( $content ) {
return array_values( array_unique( $references ) );
}
+/**
+ * Return WordPress 7.0 block-level custom CSS generated for this content fragment.
+ */
+function funkycommerce_get_rendered_block_custom_css( $content ) {
+ if (
+ ! function_exists( 'wp_styles' )
+ || ! preg_match_all( '/\bwp-custom-css-[a-z0-9-]+\b/i', $content, $matches )
+ ) {
+ return '';
+ }
+
+ $inline_styles = wp_styles()->get_data( 'wp-block-custom-css', 'after' );
+ if ( ! is_array( $inline_styles ) ) {
+ return '';
+ }
+
+ $class_names = array_values( array_unique( $matches[0] ) );
+ $matching = array_filter(
+ $inline_styles,
+ static function ( $css ) use ( $class_names ) {
+ if ( ! is_string( $css ) ) {
+ return false;
+ }
+ foreach ( $class_names as $class_name ) {
+ if ( false !== strpos( $css, '.' . $class_name ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+ );
+
+ return implode( "\n", $matching );
+}
+
+/**
+ * Run content filters with headless marker callbacks, then restore native callbacks.
+ */
+function funkycommerce_with_headless_shortcode_markers( $callback ) {
+ $content_shortcodes = funkycommerce_headless_content_shortcodes();
+ $shortcodes = array_merge( funkycommerce_headless_component_shortcodes(), $content_shortcodes );
+ $callbacks = array();
+
+ foreach ( $shortcodes as $shortcode ) {
+ $callbacks[ $shortcode ] = shortcode_exists( $shortcode ) ? $GLOBALS['shortcode_tags'][ $shortcode ] : null;
+ add_shortcode(
+ $shortcode,
+ in_array( $shortcode, $content_shortcodes, true )
+ ? 'funkycommerce_render_content_shortcode_marker'
+ : 'funkycommerce_render_headless_component_marker'
+ );
+ }
+
+ try {
+ return call_user_func( $callback );
+ } finally {
+ foreach ( $callbacks as $shortcode => $saved_callback ) {
+ if ( null === $saved_callback ) {
+ remove_shortcode( $shortcode );
+ } else {
+ $GLOBALS['shortcode_tags'][ $shortcode ] = $saved_callback;
+ }
+ }
+ }
+}
+
/**
* Render supplemental page content while preserving application shortcode markers in place.
*/
function funkycommerce_render_headless_page_content( $page_id ) {
$content = (string) get_post_field( 'post_content', $page_id );
$content = serialize_blocks( funkycommerce_filter_headless_blocks( parse_blocks( $content ) ) );
+ $content = funkycommerce_with_headless_shortcode_markers(
+ static function () use ( $content ) {
+ return apply_filters( 'the_content', $content );
+ }
+ );
+ $content = funkycommerce_security_mark_content_scripts( $content, 'page' );
+
+ /*
+ * Per-block layout rules are stored in the style engine rather than the
+ * rendered markup. Ship them with the headless fragment so constrained,
+ * flex, grid, and child-sizing controls render exactly as WordPress saved.
+ */
+ if ( function_exists( 'wp_style_engine_get_stylesheet_from_context' ) ) {
+ $block_support_styles = wp_style_engine_get_stylesheet_from_context(
+ 'block-supports',
+ array(
+ 'optimize' => true,
+ 'prettify' => false,
+ )
+ );
+
+ if ( $block_support_styles ) {
+ $content .= '';
+ }
+ }
+
+ $block_custom_css = funkycommerce_get_rendered_block_custom_css( $content );
+ if ( $block_custom_css ) {
+ $content .= '';
+ }
+
+ return $content;
+}
+
+/**
+ * Render a post field with an explicit content type for editor-script scoping.
+ */
+function funkycommerce_render_headless_content_field( $post_id, $field, $filter ) {
+ $post_type = get_post_type( $post_id );
+ if ( ! in_array( $post_type, array( 'post', 'product' ), true ) ) {
+ return '';
+ }
+
+ $content = (string) get_post_field( $field, $post_id );
+ $content = funkycommerce_with_headless_shortcode_markers(
+ static function () use ( $content, $filter ) {
+ return apply_filters( $filter, $content );
+ }
+ );
+ return funkycommerce_security_mark_content_scripts( $content, $post_type );
+}
+
+/**
+ * Request the bundled docs enhancer without shipping executable editor content.
+ *
+ * Existing published docs are also detected by their known DOM shape in the
+ * storefront, so this remains backwards-compatible until WordPress is updated.
+ */
+function funkycommerce_mark_docs_navigation_behavior( $content ) {
+ if (
+ false === strpos( $content, 'id="doc-sidebar"' )
+ || false === strpos( $content, 'id="docs-content"' )
+ || false === strpos( $content, 'id="scroll-spy"' )
+ ) {
+ return $content;
+ }
+
+ $content = preg_replace( '/
'
'