diff --git a/.github/workflows/website-docs.yml b/.github/workflows/website-docs.yml
new file mode 100644
index 0000000000..0f4aed0797
--- /dev/null
+++ b/.github/workflows/website-docs.yml
@@ -0,0 +1,84 @@
+name: Build Hugo Website
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ paths:
+ - 'docs/website/**'
+ - 'docs/developer-guide/**'
+ - 'CodenameOne/src/**'
+ - 'Ports/CLDC11/src/**'
+ - 'maven/javadoc-resources/**'
+ - '.github/scripts/build_javadocs.sh'
+ - 'scripts/website/**'
+ - '.github/workflows/website-docs.yml'
+ push:
+ branches: [main, master]
+ paths:
+ - 'docs/website/**'
+ - 'docs/developer-guide/**'
+ - 'CodenameOne/src/**'
+ - 'Ports/CLDC11/src/**'
+ - 'maven/javadoc-resources/**'
+ - '.github/scripts/build_javadocs.sh'
+ - 'scripts/website/**'
+ - '.github/workflows/website-docs.yml'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Set up Hugo
+ uses: peaceiris/actions-hugo@v3
+ with:
+ hugo-version: 'latest'
+ extended: true
+
+ - name: Set up Java 25
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: '25'
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+
+ - name: Install Asciidoctor tooling
+ run: |
+ set -euo pipefail
+ gem install --no-document asciidoctor asciidoctor-pdf rouge
+
+ - name: Build website
+ run: |
+ set -euo pipefail
+ scripts/website/build.sh
+ env:
+ WEBSITE_INCLUDE_JAVADOCS: "true"
+ WEBSITE_INCLUDE_DEVGUIDE: "true"
+
+ - name: Upload built site artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: website-preview
+ path: docs/website/public
+ if-no-files-found: error
+
+ - name: Deploy to Cloudflare Pages
+ if: ${{ github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master') }}
+ uses: cloudflare/wrangler-action@v3
+ with:
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ command: >-
+ pages deploy docs/website/public
+ --project-name=${{ vars.CLOUDFLARE_PAGES_PROJECT_NAME || secrets.CLOUDFLARE_PAGES_PROJECT_NAME || 'codenameone' }}
+ --branch=${{ github.ref_name }}
diff --git a/.gitignore b/.gitignore
index 53d7701c21..0ea567bf74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,3 +82,12 @@ pom.xml.tag
!.brokk/review.md
!.brokk/project.properties
dependency-reduced-pom.xml
+
+# Hugo website generated artifacts
+/docs/website/.hugo_build.lock
+/docs/website/hugo_stats.json
+/docs/website/resources/
+/docs/website/public/
+/docs/website/.cache/
+/docs/website/.venv/
+/docs/website/.venv-pagefind/
diff --git a/docs/website/archetypes/default.md b/docs/website/archetypes/default.md
new file mode 100644
index 0000000000..25b67521d3
--- /dev/null
+++ b/docs/website/archetypes/default.md
@@ -0,0 +1,5 @@
++++
+date = '{{ .Date }}'
+draft = true
+title = '{{ replace .File.ContentBaseName "-" " " | title }}'
++++
diff --git a/docs/website/assets/css/extended/cn1-blog-index.css b/docs/website/assets/css/extended/cn1-blog-index.css
new file mode 100644
index 0000000000..8796698a2c
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-blog-index.css
@@ -0,0 +1,155 @@
+.cn1-blog-index {
+ max-width: min(1220px, calc(100vw - 32px));
+ margin: 0 auto;
+ padding: 1.2rem 0 2rem;
+}
+
+.cn1-blog-index .post-header {
+ margin-bottom: 1.2rem;
+}
+
+.cn1-blog-index .post-title {
+ margin-bottom: 0.5rem;
+}
+
+.cn1-blog-index .post-description {
+ color: var(--secondary);
+}
+
+.cn1-blog-index__grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+ margin-top: 1rem;
+}
+
+.cn1-blog-index__card {
+ display: grid;
+ grid-template-columns: minmax(160px, 220px) minmax(0, 1fr);
+ gap: 0.9rem;
+ padding: 0.8rem;
+ border: 1px solid var(--border);
+ background: var(--entry);
+}
+
+.cn1-blog-index__thumb-link {
+ display: block;
+ align-self: start;
+ overflow: hidden;
+ aspect-ratio: 16 / 9;
+ background: var(--tertiary);
+}
+
+.cn1-blog-index__thumb {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ object-position: center;
+ display: block;
+ transform: scale(1.03);
+ transition: transform 220ms ease;
+}
+
+.cn1-blog-index__card:hover .cn1-blog-index__thumb {
+ transform: scale(1.08);
+}
+
+.cn1-blog-index__body {
+ min-width: 0;
+}
+
+.cn1-blog-index__title {
+ margin: 0 0 0.35rem;
+ font-size: 1.1rem;
+ line-height: 1.35;
+}
+
+.cn1-blog-index__title a {
+ color: var(--primary);
+ text-decoration: none;
+}
+
+.cn1-blog-index__title a:hover {
+ color: var(--tertiary);
+}
+
+.cn1-blog-index__meta {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin: 0 0 0.45rem;
+ font-size: 0.82rem;
+ color: var(--secondary);
+}
+
+.cn1-blog-index__summary {
+ margin: 0;
+ color: var(--content);
+ font-size: 0.92rem;
+ line-height: 1.55;
+}
+
+.cn1-blog-index__pagination {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin-top: 1.5rem;
+}
+
+.cn1-page-numbers {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.cn1-page-link {
+ min-width: 2rem;
+ height: 2rem;
+ padding: 0 0.55rem;
+ border: 1px solid var(--border);
+ background: var(--entry);
+ color: var(--primary);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ font-size: 0.86rem;
+}
+
+.cn1-page-ellipsis {
+ color: var(--secondary);
+ padding: 0 0.2rem;
+ font-weight: 600;
+}
+
+.cn1-page-link:hover {
+ background: var(--tertiary);
+}
+
+.cn1-page-link.is-current {
+ background: var(--primary);
+ color: var(--theme);
+ border-color: var(--primary);
+}
+
+.cn1-page-link.is-disabled {
+ opacity: 0.45;
+}
+
+.cn1-page-link--edge {
+ min-width: 3.2rem;
+}
+
+@media (max-width: 980px) {
+ .cn1-blog-index__grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 700px) {
+ .cn1-blog-index__card {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-blog-post.css b/docs/website/assets/css/extended/cn1-blog-post.css
new file mode 100644
index 0000000000..3522c795c6
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-blog-post.css
@@ -0,0 +1,99 @@
+.cn1-blog-post .post-content {
+ max-width: 100%;
+ font-size: 17px;
+ line-height: 1.7;
+}
+
+.cn1-blog-post .post-content > p,
+.cn1-blog-post .post-content > ul,
+.cn1-blog-post .post-content > ol,
+.cn1-blog-post .post-content > blockquote,
+.cn1-blog-post .post-content > pre,
+.cn1-blog-post .post-content > table,
+.cn1-blog-post .post-content > .highlight {
+ margin-block: 1.1em;
+}
+
+.cn1-blog-post .post-content h2,
+.cn1-blog-post .post-content h3,
+.cn1-blog-post .post-content h4,
+.cn1-blog-post .post-content h5 {
+ margin-top: 1.4em;
+ margin-bottom: 0.55em;
+ line-height: 1.28;
+ letter-spacing: normal;
+ text-wrap: balance;
+}
+
+.cn1-blog-post .post-content h2 {
+ font-size: 30px;
+}
+
+.cn1-blog-post .post-content h3 {
+ font-size: 24px;
+}
+
+.cn1-blog-post .post-content h4 {
+ font-size: 20px;
+}
+
+.cn1-blog-post .post-content h5 {
+ font-size: 18px;
+}
+
+.cn1-blog-post .post-content ul,
+.cn1-blog-post .post-content ol {
+ padding-inline-start: 1.2em;
+}
+
+.cn1-blog-post .post-content li + li {
+ margin-top: 0.35em;
+}
+
+.cn1-blog-post .post-content blockquote {
+ border-left: 3px solid rgba(28, 81, 255, 0.55);
+ padding: 10px 14px;
+ background: rgba(28, 81, 255, 0.06);
+ border-radius: 8px;
+}
+
+body.dark .cn1-blog-post .post-content blockquote {
+ border-left-color: rgba(175, 193, 255, 0.65);
+ background: rgba(175, 193, 255, 0.09);
+}
+
+.cn1-blog-post .post-content table {
+ display: block;
+ overflow-x: auto;
+ border-collapse: collapse;
+ width: 100%;
+}
+
+.cn1-blog-post .post-content table th,
+.cn1-blog-post .post-content table td {
+ border: 1px solid rgba(28, 81, 255, 0.18);
+ padding: 8px 10px;
+}
+
+body.dark .cn1-blog-post .post-content table th,
+body.dark .cn1-blog-post .post-content table td {
+ border-color: rgba(175, 193, 255, 0.25);
+}
+
+@media (max-width: 900px) {
+ .cn1-blog-post .post-content {
+ font-size: 16px;
+ }
+
+ .cn1-blog-post .post-content h2 {
+ font-size: 26px;
+ }
+
+ .cn1-blog-post .post-content h3 {
+ font-size: 22px;
+ }
+
+ .cn1-blog-post .post-content h4 {
+ font-size: 19px;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-courses.css b/docs/website/assets/css/extended/cn1-courses.css
new file mode 100644
index 0000000000..1947ef29cd
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-courses.css
@@ -0,0 +1,298 @@
+:root {
+ --cn1-course-card-bg: rgba(68, 82, 123, 0.35);
+ --cn1-course-card-border: rgba(29, 82, 255, 0.55);
+ --cn1-course-soft-text: rgba(217, 228, 255, 0.85);
+ --cn1-course-link: #afc1ff;
+ --cn1-course-link-strong: #f5f8ff;
+}
+
+body.light {
+ --cn1-course-card-bg: #f3f6ff;
+ --cn1-course-card-border: #d8e2ff;
+ --cn1-course-soft-text: #4d5f8e;
+ --cn1-course-link: #1d52ff;
+ --cn1-course-link-strong: #102154;
+}
+
+.cn1-course-hub .post-header,
+.cn1-course-lesson .post-header {
+ margin-bottom: 1.75rem;
+}
+
+.cn1-course-overview__bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ margin: 2rem 0 1.5rem;
+ padding: 1rem 1.25rem;
+ border: 1px solid var(--cn1-course-card-border);
+ border-radius: 14px;
+ background: var(--cn1-course-card-bg);
+}
+
+.cn1-course-overview__bar p {
+ margin: 0;
+}
+
+.cn1-course-overview__start {
+ display: inline-block;
+ border: 1px solid var(--cn1-course-link);
+ color: var(--cn1-course-link-strong);
+ text-decoration: none;
+ font-weight: 600;
+ padding: 0.55rem 1rem;
+ border-radius: 999px;
+}
+
+.cn1-course-overview__start:hover {
+ opacity: 0.9;
+}
+
+.cn1-course-overview__grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 1rem;
+}
+
+.cn1-course-module-card {
+ border: 1px solid var(--cn1-course-card-border);
+ border-radius: 14px;
+ padding: 1rem 1rem 0.9rem;
+ background: var(--cn1-course-card-bg);
+}
+
+.cn1-course-module-card header h2 {
+ margin: 0.1rem 0 0.15rem;
+ font-size: 1.12rem;
+ color: var(--cn1-course-link-strong);
+}
+
+.cn1-course-module-card header p {
+ margin: 0;
+ color: var(--cn1-course-soft-text);
+ font-size: 0.9rem;
+}
+
+.cn1-course-module-card__eyebrow {
+ margin: 0;
+ font-size: 0.78rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--cn1-course-link);
+}
+
+.cn1-course-module-card ol {
+ margin: 0.85rem 0 0;
+ padding-left: 1.15rem;
+}
+
+.cn1-course-module-card li + li {
+ margin-top: 0.7rem;
+}
+
+.cn1-course-module-card li a {
+ color: var(--cn1-course-link-strong);
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.cn1-course-module-card li a:hover {
+ color: var(--cn1-course-link);
+}
+
+.cn1-course-module-card li p {
+ margin: 0.25rem 0 0;
+ color: var(--cn1-course-soft-text);
+ font-size: 0.9rem;
+ line-height: 1.45;
+}
+
+.cn1-training-path-card > p {
+ margin: 0.9rem 0 0;
+ color: var(--cn1-course-soft-text);
+ line-height: 1.55;
+}
+
+.cn1-training-path-card__actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.55rem;
+ margin-top: 1rem;
+}
+
+.cn1-training-path-card__actions a {
+ display: inline-block;
+ text-decoration: none;
+ color: var(--cn1-course-link-strong);
+ border: 1px solid var(--cn1-course-card-border);
+ background: rgba(8, 17, 55, 0.2);
+ border-radius: 999px;
+ padding: 0.45rem 0.9rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+}
+
+body.light .cn1-training-path-card__actions a {
+ background: rgba(255, 255, 255, 0.75);
+}
+
+.cn1-training-path-card__actions a:hover {
+ border-color: var(--cn1-course-link);
+ color: var(--cn1-course-link);
+}
+
+.cn1-training-support {
+ margin-top: 1rem;
+}
+
+.cn1-course-lesson__kicker {
+ margin: 0;
+ color: var(--cn1-course-link);
+ font-weight: 600;
+}
+
+.cn1-course-lesson__meta {
+ margin: 0.4rem 0 0;
+ color: var(--cn1-course-soft-text);
+}
+
+.cn1-course-lesson__layout {
+ display: grid;
+ grid-template-columns: 320px minmax(0, 1fr);
+ gap: 1.2rem;
+}
+
+.cn1-course-lesson__sidebar {
+ position: sticky;
+ top: 92px;
+ max-height: calc(100vh - 110px);
+ overflow: auto;
+ border: 1px solid var(--cn1-course-card-border);
+ border-radius: 14px;
+ padding: 0.9rem;
+ background: var(--cn1-course-card-bg);
+}
+
+.cn1-course-lesson__back {
+ display: inline-block;
+ margin-bottom: 0.75rem;
+ text-decoration: none;
+ color: var(--cn1-course-link);
+ font-weight: 600;
+}
+
+.cn1-course-lesson__module + .cn1-course-lesson__module {
+ margin-top: 1rem;
+ padding-top: 0.8rem;
+ border-top: 1px solid var(--cn1-course-card-border);
+}
+
+.cn1-course-lesson__module h2 {
+ margin: 0;
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--cn1-course-link);
+}
+
+.cn1-course-lesson__module > p {
+ margin: 0.3rem 0 0.5rem;
+ font-size: 0.95rem;
+ color: var(--cn1-course-link-strong);
+ font-weight: 600;
+}
+
+.cn1-course-lesson__module ol {
+ margin: 0;
+ padding-left: 1rem;
+}
+
+.cn1-course-lesson__module li + li {
+ margin-top: 0.45rem;
+}
+
+.cn1-course-lesson__module a {
+ color: var(--cn1-course-soft-text);
+ text-decoration: none;
+ font-size: 0.9rem;
+}
+
+.cn1-course-lesson__module a.is-active {
+ color: var(--cn1-course-link-strong);
+ font-weight: 700;
+}
+
+.cn1-course-lesson__content {
+ border: 1px solid var(--cn1-course-card-border);
+ border-radius: 14px;
+ padding: 1.15rem 1.2rem;
+ background: var(--cn1-course-card-bg);
+}
+
+.cn1-course-lesson__content .cn1-embed {
+ margin-top: 1.2rem;
+}
+
+.cn1-course-lesson__pager {
+ margin-top: 2rem;
+ padding-top: 1.2rem;
+ border-top: 1px solid var(--cn1-course-card-border);
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0.7rem;
+}
+
+.cn1-course-lesson__pager-link {
+ text-decoration: none;
+ border: 1px solid var(--cn1-course-card-border);
+ border-radius: 10px;
+ padding: 0.7rem 0.8rem;
+ color: var(--cn1-course-link-strong);
+ display: block;
+ background: rgba(8, 17, 55, 0.2);
+}
+
+body.light .cn1-course-lesson__pager-link {
+ background: rgba(255, 255, 255, 0.75);
+}
+
+.cn1-course-lesson__pager-link span {
+ display: block;
+ font-size: 0.75rem;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--cn1-course-link);
+}
+
+.cn1-course-lesson__pager-link strong {
+ display: block;
+ margin-top: 0.2rem;
+ line-height: 1.35;
+}
+
+.cn1-course-lesson__pager-link--next {
+ text-align: right;
+}
+
+@media (max-width: 1050px) {
+ .cn1-course-lesson__layout {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-course-lesson__sidebar {
+ position: static;
+ max-height: none;
+ }
+}
+
+@media (max-width: 760px) {
+ .cn1-course-overview__bar {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .cn1-course-lesson__pager {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-crisp.css b/docs/website/assets/css/extended/cn1-crisp.css
new file mode 100644
index 0000000000..d14267881b
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-crisp.css
@@ -0,0 +1,95 @@
+.cn1-cookie-banner {
+ position: fixed;
+ left: 18px;
+ right: 18px;
+ bottom: 18px;
+ z-index: 120;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 14px 16px;
+ background: #f2f5ff;
+ border: 1px solid rgba(28, 81, 255, 0.24);
+ border-radius: 10px;
+ box-shadow: 0 12px 32px rgba(9, 23, 64, 0.18);
+ color: #233462;
+ font-family: "Poppins", sans-serif;
+}
+
+body.dark .cn1-cookie-banner {
+ background: #1f2b4a;
+ border-color: rgba(175, 193, 255, 0.28);
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35);
+ color: #e7eeff;
+}
+
+.cn1-cookie-banner p {
+ margin: 0;
+ font-size: 13px;
+ line-height: 1.4;
+}
+
+.cn1-cookie-banner a {
+ color: #1c51ff;
+ text-decoration: underline;
+}
+
+body.dark .cn1-cookie-banner a {
+ color: #afc1ff;
+}
+
+.cn1-cookie-banner__actions {
+ display: inline-flex;
+ gap: 8px;
+ flex: 0 0 auto;
+}
+
+.cn1-cookie-banner__actions button {
+ border: 1px solid rgba(28, 81, 255, 0.35);
+ border-radius: 8px;
+ background: #ffffff;
+ color: #1b2a55;
+ font-family: "Poppins", sans-serif;
+ font-size: 12px;
+ font-weight: 600;
+ padding: 8px 12px;
+ cursor: pointer;
+}
+
+.cn1-cookie-banner__actions button[data-cn1-cookie-accept] {
+ background: #1c51ff;
+ border-color: #1c51ff;
+ color: #f5f8ff;
+}
+
+body.dark .cn1-cookie-banner__actions button {
+ background: #162446;
+ border-color: rgba(175, 193, 255, 0.38);
+ color: #e7eeff;
+}
+
+body.dark .cn1-cookie-banner__actions button[data-cn1-cookie-accept] {
+ background: #1d52ff;
+ border-color: #1d52ff;
+ color: #f5f8ff;
+}
+
+@media (max-width: 820px) {
+ .cn1-cookie-banner {
+ flex-direction: column;
+ align-items: flex-start;
+ left: 12px;
+ right: 12px;
+ bottom: 12px;
+ padding: 12px;
+ }
+
+ .cn1-cookie-banner__actions {
+ width: 100%;
+ }
+
+ .cn1-cookie-banner__actions button {
+ flex: 1;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-demo-page.css b/docs/website/assets/css/extended/cn1-demo-page.css
new file mode 100644
index 0000000000..1786b637de
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-demo-page.css
@@ -0,0 +1,219 @@
+.post-single--demo .post-header {
+ display: none;
+}
+
+.post-single--demo .post-footer {
+ display: none;
+}
+
+.post-single--demo {
+ --cn1-demo-bg: #eef2fb;
+ --cn1-demo-card: #ffffff;
+ --cn1-demo-border: #cfd8ee;
+ --cn1-demo-text: #172247;
+ --cn1-demo-muted: #4f5f8a;
+ --cn1-demo-accent: #1d52ff;
+ --cn1-demo-chip: #e9efff;
+}
+
+body.dark .post-single--demo {
+ --cn1-demo-bg: #081447;
+ --cn1-demo-card: #142257;
+ --cn1-demo-border: #2f438f;
+ --cn1-demo-text: #eff4ff;
+ --cn1-demo-muted: #afc1ff;
+ --cn1-demo-accent: #8db2ff;
+ --cn1-demo-chip: rgba(255, 255, 255, 0.08);
+}
+
+.post-single--demo {
+ background: var(--cn1-demo-bg);
+ border: 0;
+ border-radius: 0;
+ padding: 20px 0 0;
+ margin-top: 24px;
+}
+
+.cn1-demo-top {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(420px, 52%);
+ gap: 24px;
+ align-items: start;
+}
+
+.cn1-demo-page-block {
+ background: transparent;
+ border: 0;
+ border-radius: 0;
+ padding: 18px 16px;
+}
+
+.cn1-demo-eyebrow {
+ margin: 0;
+ color: var(--cn1-demo-accent);
+ font-family: "Poppins", sans-serif;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+}
+
+.cn1-demo-page-block h2 {
+ margin: 6px 0 8px;
+ color: var(--cn1-demo-text);
+ font-family: "Poppins", sans-serif;
+ font-size: clamp(26px, 3vw, 38px);
+ line-height: 1.12;
+ font-weight: 600;
+}
+
+.cn1-demo-summary {
+ margin: 0;
+ color: var(--cn1-demo-muted);
+ font-family: "Poppins", sans-serif;
+ font-size: 16px;
+ line-height: 1.4;
+}
+
+.cn1-demo-actions {
+ margin-top: 14px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.cn1-demo-pill {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px 14px;
+ border-radius: 999px;
+ border: 1px solid var(--cn1-demo-accent);
+ background: var(--cn1-demo-accent);
+ color: #fff;
+ text-decoration: none;
+ font-family: "Poppins", sans-serif;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 1;
+}
+
+.cn1-demo-pill--ghost {
+ background: var(--cn1-demo-chip);
+ color: var(--cn1-demo-text);
+}
+
+.cn1-demo-carousel {
+ margin-top: 0;
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ grid-template-rows: auto auto;
+ gap: 10px 12px;
+ align-items: center;
+}
+
+.cn1-demo-carousel__viewport {
+ overflow: hidden;
+ border: 1px solid var(--cn1-demo-border);
+ border-radius: 0;
+ background: #0a1a59;
+}
+
+.cn1-demo-carousel__track {
+ display: flex;
+ transition: transform 240ms ease;
+}
+
+.cn1-demo-carousel__slide {
+ min-width: 100%;
+ margin: 0;
+}
+
+.cn1-demo-carousel__slide img {
+ width: 100%;
+ max-height: 640px;
+ object-fit: contain;
+ display: block;
+}
+
+.cn1-demo-carousel__nav {
+ width: 40px;
+ height: 40px;
+ border-radius: 0;
+ border: 1px solid var(--cn1-demo-border);
+ background: var(--cn1-demo-card);
+ color: var(--cn1-demo-text);
+ cursor: pointer;
+}
+
+.cn1-demo-carousel__dots {
+ grid-column: 1 / -1;
+ display: flex;
+ justify-content: center;
+ gap: 8px;
+}
+
+.cn1-demo-carousel__dots button {
+ width: 10px;
+ height: 10px;
+ border-radius: 0;
+ border: 0;
+ background: color-mix(in srgb, var(--cn1-demo-accent) 35%, transparent);
+ cursor: pointer;
+}
+
+.cn1-demo-carousel__dots button[aria-current="true"] {
+ background: var(--cn1-demo-accent);
+}
+
+.post-single--demo .cn1-demo-details {
+ margin-top: 16px;
+ background: var(--cn1-demo-card);
+ border: 1px solid var(--cn1-demo-border);
+ border-radius: 0;
+ padding: 26px 24px;
+}
+
+.post-single--demo .cn1-demo-details h2:first-child,
+.post-single--demo .cn1-demo-details h2#screenshots {
+ display: none;
+}
+
+.post-single--demo .cn1-demo-details > p:first-child {
+ display: none;
+}
+
+.post-single--demo .cn1-demo-details > p > img {
+ display: none;
+}
+
+.post-single--demo .cn1-demo-details > ul:first-of-type {
+ display: none;
+}
+
+.post-single--demo .cn1-demo-details img {
+ border-radius: 0;
+ border: 1px solid var(--cn1-demo-border);
+}
+
+@media (max-width: 900px) {
+ .post-single--demo {
+ padding: 12px 0 0;
+ }
+
+ .cn1-demo-top {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-demo-carousel {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-demo-carousel__nav {
+ display: none;
+ }
+
+ .post-single--demo .cn1-demo-details {
+ padding: 18px 16px;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-demos.css b/docs/website/assets/css/extended/cn1-demos.css
new file mode 100644
index 0000000000..7754444ff5
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-demos.css
@@ -0,0 +1,171 @@
+.cn1-demos-page {
+ --cn1-demos-bg: #eef1f8;
+ --cn1-demos-card: #ffffff;
+ --cn1-demos-border: #cfd8ee;
+ --cn1-demos-text: #172247;
+ --cn1-demos-muted: #4f5f8a;
+ --cn1-demos-accent: #1d52ff;
+ --cn1-demos-chip: #e9efff;
+ background: var(--cn1-demos-bg);
+ color: var(--cn1-demos-text);
+ width: 100vw;
+ margin-left: calc(50% - 50vw);
+ margin-right: calc(50% - 50vw);
+ padding: 28px 0 64px;
+}
+
+body.dark .cn1-demos-page {
+ --cn1-demos-bg: #071246;
+ --cn1-demos-card: #142257;
+ --cn1-demos-border: #2f438f;
+ --cn1-demos-text: #eff4ff;
+ --cn1-demos-muted: #afc1ff;
+ --cn1-demos-accent: #8db2ff;
+ --cn1-demos-chip: rgba(255, 255, 255, 0.08);
+}
+
+.cn1-demos-hero,
+.cn1-demos-grid {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 0 32px;
+}
+
+.cn1-demos-eyebrow {
+ margin: 0;
+ color: var(--cn1-demos-accent);
+ font-family: "Poppins", sans-serif;
+ font-size: 14px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ font-weight: 700;
+}
+
+.cn1-demos-hero h1 {
+ margin: 8px 0 10px;
+ font-family: "Poppins", sans-serif;
+ font-size: clamp(32px, 4vw, 54px);
+ line-height: 1.05;
+ color: var(--cn1-demos-text);
+}
+
+.cn1-demos-subtitle {
+ margin: 0;
+ font-family: "Poppins", sans-serif;
+ color: var(--cn1-demos-muted);
+ font-size: 17px;
+ font-weight: 500;
+}
+
+.cn1-demos-grid {
+ margin-top: 28px;
+ display: grid;
+ gap: 20px;
+ grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
+}
+
+.cn1-demo-card {
+ display: grid;
+ grid-template-rows: auto 1fr;
+ gap: 0;
+ border: 1px solid var(--cn1-demos-border);
+ border-radius: 14px;
+ overflow: hidden;
+ background: var(--cn1-demos-card);
+ box-shadow: 0 10px 24px rgba(8, 16, 45, 0.08);
+ transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease;
+}
+
+.cn1-demo-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 16px 32px rgba(8, 16, 45, 0.14);
+ border-color: color-mix(in srgb, var(--cn1-demos-accent) 45%, var(--cn1-demos-border));
+}
+
+.cn1-demo-card__thumb {
+ display: block;
+ background: linear-gradient(145deg, #2a5fff 0%, #1f4ce0 100%);
+}
+
+.cn1-demo-card__thumb img {
+ width: 100%;
+ aspect-ratio: 16 / 10;
+ object-fit: cover;
+ mix-blend-mode: screen;
+}
+
+body.dark .cn1-demo-card__thumb img {
+ mix-blend-mode: normal;
+}
+
+.cn1-demo-card__body {
+ padding: 20px 24px;
+ display: flex;
+ flex-direction: column;
+}
+
+.cn1-demo-card__body h2 {
+ margin: 0;
+ font-family: "Poppins", sans-serif;
+ font-size: 28px;
+ font-weight: 600;
+ line-height: 1.15;
+}
+
+.cn1-demo-card__body h2 a {
+ color: var(--cn1-demos-text);
+ text-decoration: none;
+}
+
+.cn1-demo-card__body h2 a:hover {
+ color: var(--cn1-demos-accent);
+}
+
+.cn1-demo-card__body p {
+ margin: 12px 0 0;
+ color: var(--cn1-demos-muted);
+ font-family: "Poppins", sans-serif;
+ font-size: 15px;
+ line-height: 1.45;
+}
+
+.cn1-demo-card__actions {
+ margin-top: 16px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-top: auto;
+ padding-top: 10px;
+}
+
+.cn1-demo-pill {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px 14px;
+ border-radius: 999px;
+ border: 1px solid var(--cn1-demos-accent);
+ background: var(--cn1-demos-accent);
+ color: #fff;
+ text-decoration: none;
+ font-family: "Poppins", sans-serif;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 1;
+}
+
+.cn1-demo-pill--ghost {
+ background: var(--cn1-demos-chip);
+ color: var(--cn1-demos-text);
+}
+
+@media (max-width: 900px) {
+ .cn1-demos-hero,
+ .cn1-demos-grid {
+ padding: 0 16px;
+ }
+
+ .cn1-demos-grid {
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-developing.css b/docs/website/assets/css/extended/cn1-developing.css
new file mode 100644
index 0000000000..4a26e742cb
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-developing.css
@@ -0,0 +1,164 @@
+.post-header--with-hero {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) clamp(240px, 30vw, 420px);
+ align-items: center;
+ gap: 28px;
+}
+
+.post-header--with-hero .post-header__content {
+ min-width: 0;
+}
+
+.post-header--with-hero .post-header__hero img {
+ width: 100%;
+ height: auto;
+ display: block;
+}
+
+.post-single--developing-in-codename-one .post-content {
+ padding: 6px 0 0;
+}
+
+.post-single--developing-in-codename-one .post-content > h2 {
+ font-size: clamp(28px, 2.4vw, 34px);
+ margin-top: 8px;
+ margin-bottom: 16px;
+ clear: both;
+}
+
+.post-single--developing-in-codename-one .post-content > h2 + p + ul {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 30px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+ margin-right: clamp(280px, 36vw, 450px);
+}
+
+.post-single--developing-in-codename-one .post-content > h2 + p + ul li {
+ margin: 0;
+}
+
+.post-single--developing-in-codename-one .post-content > h2 + p + ul a {
+ display: block;
+ padding: 14px 16px;
+ border: 1px solid rgba(29, 82, 255, 0.35);
+ border-radius: 12px;
+ background: rgba(255, 255, 255, 0.72);
+ text-decoration: none;
+ box-shadow: 0 6px 16px rgba(17, 42, 116, 0.08);
+ transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
+}
+
+.post-single--developing-in-codename-one .post-content > h2 + p + ul a:hover {
+ transform: translateY(-1px);
+ border-color: rgba(29, 82, 255, 0.65);
+ box-shadow: 0 10px 20px rgba(17, 42, 116, 0.12);
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(1) + p,
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p {
+ width: clamp(260px, 32vw, 420px);
+ float: right;
+ margin: 4px 0 18px 24px;
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(1) + p img,
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p img {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.post-single--developing-in-codename-one .post-content img[src*="Group-2197.svg"] {
+ display: block;
+ margin: 8px auto 24px;
+ max-width: min(100%, 520px);
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 28px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ margin-right: clamp(280px, 36vw, 450px);
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul li {
+ margin: 0;
+ padding: 14px 16px;
+ border: 1px solid rgba(29, 82, 255, 0.22);
+ border-radius: 12px;
+ background: rgba(255, 255, 255, 0.72);
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul li p {
+ margin: 0;
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul li strong a {
+ text-decoration: none;
+ font-size: 20px;
+ line-height: 1.25;
+ color: inherit;
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul li strong {
+ display: block;
+ margin-bottom: 8px;
+}
+
+.post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul::after,
+.post-single--developing-in-codename-one .post-content > h2 + p + ul::after {
+ content: "";
+ display: block;
+ clear: both;
+}
+
+body.dark .post-single--developing-in-codename-one .post-content {
+ background: transparent;
+ border-color: transparent;
+}
+
+body.dark .post-single--developing-in-codename-one .post-content > h2 + p + ul a,
+body.dark .post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul li {
+ background: rgba(25, 36, 81, 0.3);
+ border-color: rgba(168, 190, 255, 0.3);
+ box-shadow: none;
+}
+
+body.dark .post-single--developing-in-codename-one .post-content > h2 + p + ul a:hover {
+ border-color: rgba(168, 190, 255, 0.6);
+}
+
+@media (max-width: 900px) {
+ .post-header--with-hero {
+ grid-template-columns: 1fr;
+ gap: 16px;
+ }
+
+ .post-header--with-hero .post-header__hero img {
+ max-width: 320px;
+ margin: 0 auto;
+ }
+
+ .post-single--developing-in-codename-one .post-content > h2 + p + ul {
+ grid-template-columns: 1fr;
+ margin-right: 0;
+ }
+
+ .post-single--developing-in-codename-one .post-content > h2:nth-of-type(1) + p,
+ .post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p {
+ float: none;
+ width: 100%;
+ margin: 16px auto;
+ }
+
+ .post-single--developing-in-codename-one .post-content > h2:nth-of-type(2) + p + p + ul {
+ grid-template-columns: 1fr;
+ margin-right: 0;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-development-environment.css b/docs/website/assets/css/extended/cn1-development-environment.css
new file mode 100644
index 0000000000..d5ca0adf32
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-development-environment.css
@@ -0,0 +1,225 @@
+.post-single--development-environment .post-content {
+ --cn1-dev-blue: #1d52ff;
+ --cn1-dev-soft: rgba(29, 82, 255, 0.08);
+ --cn1-dev-soft-border: rgba(29, 82, 255, 0.24);
+ --cn1-dev-surface: rgba(29, 82, 255, 0.03);
+}
+
+body.dark .post-single--development-environment .post-content {
+ --cn1-dev-blue: #afc1ff;
+ --cn1-dev-soft: rgba(175, 193, 255, 0.14);
+ --cn1-dev-soft-border: rgba(175, 193, 255, 0.3);
+ --cn1-dev-surface: rgba(175, 193, 255, 0.07);
+}
+
+.post-single--development-environment .post-content > p:first-of-type {
+ font-size: 1.1rem;
+ line-height: 1.72;
+ font-weight: 500;
+ margin-bottom: 1rem;
+}
+
+.post-single--development-environment .post-content img[src*="Group-2326.svg"] {
+ float: right;
+ width: min(470px, 46%);
+ margin: 0.2rem 0 1rem 1.6rem;
+}
+
+.post-single--development-environment .post-content > h2 {
+ clear: both;
+ margin-top: 2.1rem;
+ margin-bottom: 0.85rem;
+ padding: 0.58rem 0.95rem;
+ border-left: 4px solid var(--cn1-dev-blue);
+ background: var(--cn1-dev-soft);
+ color: var(--cn1-dev-blue);
+}
+
+.post-single--development-environment .post-content > h3 {
+ margin-top: 1.2rem;
+ margin-bottom: 0.45rem;
+ color: var(--cn1-dev-blue);
+}
+
+.post-single--development-environment .post-content .cn1-dev-value-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(180px, 1fr));
+ gap: 0.8rem;
+ margin: 0.35rem 0 1.35rem;
+}
+
+.post-single--development-environment .post-content .cn1-dev-value-item {
+ background: var(--cn1-dev-surface);
+ border: 1px solid var(--cn1-dev-soft-border);
+ padding: 0.9rem;
+}
+
+.post-single--development-environment .post-content .cn1-dev-value-item svg {
+ width: 1.35rem;
+ height: 1.35rem;
+ color: var(--cn1-dev-blue);
+ stroke: currentColor;
+ fill: none;
+ stroke-width: 1.7;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ display: block;
+}
+
+.post-single--development-environment .post-content .cn1-dev-value-item h3 {
+ margin: 0.5rem 0 0.35rem;
+ font-size: 1.06rem;
+ color: var(--cn1-dev-blue);
+}
+
+.post-single--development-environment .post-content .cn1-dev-value-item p {
+ margin: 0;
+ font-size: 0.94rem;
+ line-height: 1.55;
+}
+
+.post-single--development-environment .post-content .cn1-ide-strip {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(150px, 1fr));
+ gap: 0.65rem;
+ margin: 0.35rem 0 1.4rem;
+}
+
+.post-single--development-environment .post-content .cn1-ide-item {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.45rem;
+ min-height: 56px;
+ padding: 0.45rem 0.7rem;
+ background: var(--cn1-dev-surface);
+ border: 1px solid var(--cn1-dev-soft-border);
+}
+
+.post-single--development-environment .post-content .cn1-ide-item img {
+ width: 26px;
+ height: 26px;
+ object-fit: contain;
+}
+
+.post-single--development-environment .post-content .cn1-ide-item i {
+ color: var(--cn1-dev-blue);
+ font-size: 1.2rem;
+}
+
+.post-single--development-environment .post-content .cn1-ide-item span {
+ color: var(--cn1-dev-blue);
+ font-weight: 600;
+ font-size: 0.94rem;
+}
+
+.post-single--development-environment .post-content .cn1-flow-stack {
+ display: grid;
+ gap: 0.85rem;
+ margin: 0.35rem 0 1.35rem;
+}
+
+.post-single--development-environment .post-content .cn1-step-card {
+ display: grid;
+ grid-template-columns: minmax(220px, 0.95fr) minmax(320px, 1.6fr);
+ gap: 0.95rem;
+ align-items: center;
+ background: var(--cn1-dev-surface);
+ border: 1px solid var(--cn1-dev-soft-border);
+ padding: 0.8rem;
+}
+
+.post-single--development-environment .post-content .cn1-step-card--reverse .cn1-step-copy {
+ order: 2;
+}
+
+.post-single--development-environment .post-content .cn1-step-card--reverse .cn1-step-media {
+ order: 1;
+}
+
+.post-single--development-environment .post-content .cn1-step-kicker {
+ display: inline-block;
+ margin-bottom: 0.35rem;
+ color: var(--cn1-dev-blue);
+ font-weight: 700;
+ font-size: 0.78rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.post-single--development-environment .post-content .cn1-step-copy h3 {
+ margin: 0 0 0.38rem;
+ color: var(--cn1-dev-blue);
+ font-size: 1.22rem;
+}
+
+.post-single--development-environment .post-content .cn1-step-copy p {
+ margin: 0;
+ line-height: 1.58;
+}
+
+.post-single--development-environment .post-content .cn1-step-media img {
+ margin: 0;
+ width: 100%;
+ display: block;
+}
+
+.post-single--development-environment .post-content .cn1-highlight-pair {
+ display: grid;
+ grid-template-columns: minmax(220px, 0.9fr) minmax(340px, 1.6fr);
+ gap: 1rem;
+ align-items: center;
+ margin: 0.2rem 0 1rem;
+ padding: 0.8rem 0.95rem;
+ background: var(--cn1-dev-soft);
+ border-left: 4px solid var(--cn1-dev-blue);
+}
+
+.post-single--development-environment .post-content .cn1-highlight-copy h4 {
+ margin: 0 0 0.4rem;
+ color: var(--cn1-dev-blue);
+ font-size: 1.16rem;
+}
+
+.post-single--development-environment .post-content .cn1-highlight-copy p {
+ margin: 0;
+}
+
+.post-single--development-environment .post-content .cn1-highlight-media img {
+ margin: 0;
+ width: 100%;
+ display: block;
+}
+
+.post-single--development-environment .post-content::after {
+ content: "";
+ display: block;
+ clear: both;
+}
+
+@media (max-width: 980px) {
+ .post-single--development-environment .post-content img[src*="Group-2326.svg"] {
+ float: none;
+ width: min(440px, 100%);
+ margin: 0.45rem auto 1rem;
+ display: block;
+ }
+
+ .post-single--development-environment .post-content .cn1-dev-value-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .post-single--development-environment .post-content .cn1-ide-strip {
+ grid-template-columns: repeat(2, minmax(150px, 1fr));
+ }
+
+ .post-single--development-environment .post-content .cn1-step-card,
+ .post-single--development-environment .post-content .cn1-highlight-pair {
+ grid-template-columns: 1fr;
+ }
+
+ .post-single--development-environment .post-content .cn1-step-card--reverse .cn1-step-copy,
+ .post-single--development-environment .post-content .cn1-step-card--reverse .cn1-step-media {
+ order: initial;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-faq.css b/docs/website/assets/css/extended/cn1-faq.css
new file mode 100644
index 0000000000..5123e75a9e
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-faq.css
@@ -0,0 +1,91 @@
+.post-single--faq .post-header {
+ margin-bottom: 1.25rem;
+ padding: 1.25rem 1.25rem 1.1rem;
+ border: 1px solid rgba(29, 82, 255, 0.25);
+ border-radius: 14px;
+ background: linear-gradient(140deg, rgba(29, 82, 255, 0.12), rgba(29, 82, 255, 0.02));
+}
+
+body.dark .post-single--faq .post-header {
+ border-color: rgba(175, 193, 255, 0.25);
+ background: linear-gradient(140deg, rgba(36, 66, 158, 0.3), rgba(7, 20, 60, 0.35));
+}
+
+.post-single--faq .post-content > h2 {
+ margin-top: 2rem;
+ margin-bottom: 0.6rem;
+ padding: 0.7rem 0.9rem;
+ border-left: 4px solid #1d52ff;
+ border-radius: 10px;
+ background: rgba(29, 82, 255, 0.08);
+}
+
+body.dark .post-single--faq .post-content > h2 {
+ border-left-color: #afc1ff;
+ background: rgba(175, 193, 255, 0.12);
+}
+
+.post-single--faq .post-content > h3 {
+ margin-top: 1.25rem;
+ margin-bottom: 0.35rem;
+ font-size: 1.2rem;
+}
+
+.post-single--faq .post-content > h3::before {
+ content: "Q";
+ display: inline-flex;
+ width: 1.35rem;
+ height: 1.35rem;
+ align-items: center;
+ justify-content: center;
+ margin-right: 0.5rem;
+ border-radius: 50%;
+ font-size: 0.8rem;
+ font-weight: 700;
+ color: #f5f8ff;
+ background: #1d52ff;
+ vertical-align: 0.05rem;
+}
+
+body.dark .post-single--faq .post-content > h3::before {
+ color: #061033;
+ background: #afc1ff;
+}
+
+.post-single--faq .post-content > p,
+.post-single--faq .post-content > ul {
+ margin-left: 1.9rem;
+}
+
+.post-single--faq .post-content > blockquote {
+ margin: 0.9rem 0 1.2rem;
+ border-left: 4px solid #1d52ff;
+ border-radius: 10px;
+ background: rgba(29, 82, 255, 0.06);
+ padding: 0.8rem 1rem;
+}
+
+body.dark .post-single--faq .post-content > blockquote {
+ border-left-color: #afc1ff;
+ background: rgba(175, 193, 255, 0.14);
+}
+
+.post-single--faq .post-content > ul:last-child {
+ margin-left: 0;
+ padding: 1rem;
+ border: 1px solid rgba(29, 82, 255, 0.2);
+ border-radius: 12px;
+ background: rgba(29, 82, 255, 0.05);
+}
+
+body.dark .post-single--faq .post-content > ul:last-child {
+ border-color: rgba(175, 193, 255, 0.35);
+ background: rgba(175, 193, 255, 0.08);
+}
+
+@media (max-width: 900px) {
+ .post-single--faq .post-content > p,
+ .post-single--faq .post-content > ul {
+ margin-left: 0.6rem;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-getting-started.css b/docs/website/assets/css/extended/cn1-getting-started.css
new file mode 100644
index 0000000000..1b0043842f
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-getting-started.css
@@ -0,0 +1,310 @@
+.cn1-gs-page {
+ --cn1-gs-bg: #dfe2ea;
+ --cn1-gs-card-bg: #f6f7fa;
+ --cn1-gs-card-border: #d5d9e1;
+ --cn1-gs-text: #0e1b49;
+ --cn1-gs-muted: #4d5160;
+ --cn1-gs-crumb: #9ba2b5;
+ --cn1-gs-accent: #2c57e6;
+ --cn1-gs-separator: #e1e3e8;
+ --cn1-gs-cta-bg: #a6cd2b;
+ --cn1-gs-cta-bg-hover: #b6dd36;
+ --cn1-gs-cta-text: #34403b;
+ --cn1-gs-media-bg: #ffffff;
+ background: #dfe2ea;
+ color: #0e1b49;
+ padding: 22px 0 84px;
+ width: 100vw;
+ margin-left: calc(50% - 50vw);
+ margin-right: calc(50% - 50vw);
+}
+
+body.dark .cn1-gs-page {
+ --cn1-gs-bg: #0a1438;
+ --cn1-gs-card-bg: #131e4b;
+ --cn1-gs-card-border: #2a3a77;
+ --cn1-gs-text: #e7ecff;
+ --cn1-gs-muted: #c2cdee;
+ --cn1-gs-crumb: #9cb2eb;
+ --cn1-gs-accent: #7fa5ff;
+ --cn1-gs-separator: #2a3a77;
+ --cn1-gs-cta-bg: #9dc62a;
+ --cn1-gs-cta-bg-hover: #b0d83e;
+ --cn1-gs-cta-text: #1f2d52;
+ --cn1-gs-media-bg: #0f1a46;
+}
+
+.cn1-gs-page {
+ background: var(--cn1-gs-bg);
+ color: var(--cn1-gs-text);
+}
+
+.cn1-gs-hero {
+ max-width: 1400px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ align-items: center;
+ gap: 26px;
+ padding: 48px 72px 44px;
+}
+
+.cn1-gs-breadcrumbs {
+ display: inline-flex;
+ align-items: center;
+ gap: 14px;
+ margin-bottom: 20px;
+}
+
+.cn1-gs-breadcrumbs a,
+.cn1-gs-breadcrumbs span {
+ color: var(--cn1-gs-crumb);
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.cn1-gs-breadcrumbs a:hover {
+ color: var(--cn1-gs-accent);
+}
+
+.cn1-gs-hero h1 {
+ margin: 0;
+ color: var(--cn1-gs-accent);
+ font-family: "Poppins", sans-serif;
+ font-size: 40px;
+ line-height: 1.05;
+ font-weight: 600;
+}
+
+.cn1-gs-hero p {
+ margin-top: 14px;
+ color: var(--cn1-gs-muted);
+ font-family: "Poppins", sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.2;
+}
+
+.cn1-gs-hero__art {
+ display: flex;
+ justify-content: center;
+}
+
+.cn1-gs-hero__art img {
+ width: 100%;
+ max-width: 520px;
+ height: auto;
+}
+
+.cn1-gs-step-card {
+ max-width: 1580px;
+ margin: 0 auto 44px;
+ background: var(--cn1-gs-card-bg);
+ border: 1px solid var(--cn1-gs-card-border);
+ border-radius: 10px;
+ padding: 56px 72px 48px;
+}
+
+.cn1-gs-step-label {
+ margin: 0 0 12px;
+ color: var(--cn1-gs-accent);
+ font-family: "Poppins", sans-serif;
+ font-size: 13px;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+}
+
+.cn1-gs-step-card h2 {
+ margin: 0;
+ color: var(--cn1-gs-text);
+ font-family: "Poppins", sans-serif;
+ font-size: 28px;
+ font-weight: 600;
+ line-height: 1.14;
+ padding-bottom: 20px;
+ border-bottom: 2px solid var(--cn1-gs-separator);
+}
+
+.cn1-gs-step-row {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+ gap: 28px;
+ padding: 24px 0 30px;
+}
+
+.cn1-gs-step-row p {
+ margin: 0;
+ color: var(--cn1-gs-muted);
+ font-family: "Poppins", sans-serif;
+ font-size: 15px;
+ line-height: 1.45;
+ font-weight: 500;
+}
+
+.cn1-gs-cta {
+ display: inline-block;
+ background: var(--cn1-gs-cta-bg);
+ color: var(--cn1-gs-cta-text);
+ padding: 12px 30px;
+ border-radius: 8px;
+ font-family: "Poppins", sans-serif;
+ font-size: 15px;
+ font-weight: 600;
+ text-decoration: none;
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
+}
+
+.cn1-gs-cta:hover {
+ background: var(--cn1-gs-cta-bg-hover);
+}
+
+.cn1-gs-video {
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.cn1-gs-video iframe {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ border: 0;
+ display: block;
+}
+
+.cn1-gs-build-grid {
+ display: grid;
+ grid-template-columns: 0.9fr 1fr 1fr;
+ gap: 36px;
+ padding-top: 30px;
+ align-items: start;
+}
+
+.cn1-gs-build-copy p {
+ margin: 0;
+ color: var(--cn1-gs-text);
+ font-family: "Poppins", sans-serif;
+ font-size: 22px;
+ line-height: 1.28;
+ font-weight: 500;
+}
+
+.cn1-gs-resource-card img {
+ width: 100%;
+ max-height: 175px;
+ object-fit: contain;
+ background: var(--cn1-gs-media-bg);
+ border-radius: 8px;
+ display: block;
+}
+
+.cn1-gs-resource-card p {
+ margin: 16px 0 0;
+ color: var(--cn1-gs-muted);
+ font-family: "Poppins", sans-serif;
+ font-size: 14px;
+ line-height: 1.45;
+ font-weight: 500;
+}
+
+.cn1-gs-resource-card > a,
+.cn1-gs-links a {
+ margin-top: 14px;
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+ color: var(--cn1-gs-muted);
+ font-family: "Poppins", sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ text-decoration: none;
+ border-top: 2px solid var(--cn1-gs-separator);
+ padding-top: 14px;
+}
+
+.cn1-gs-resource-card > a:hover,
+.cn1-gs-links a:hover {
+ color: var(--cn1-gs-accent);
+}
+
+.cn1-gs-links {
+ display: grid;
+ gap: 12px;
+}
+
+@media (max-width: 1200px) {
+ .cn1-gs-hero {
+ grid-template-columns: 1fr;
+ padding: 34px 22px 24px;
+ }
+
+ .cn1-gs-step-card {
+ margin: 0 18px 26px;
+ padding: 34px 24px;
+ }
+
+ .cn1-gs-hero h1 {
+ font-size: 34px;
+ }
+
+ .cn1-gs-hero p {
+ font-size: 16px;
+ }
+
+ .cn1-gs-breadcrumbs a,
+ .cn1-gs-breadcrumbs span {
+ font-size: 18px;
+ }
+
+ .cn1-gs-step-label {
+ font-size: 14px;
+ }
+
+ .cn1-gs-step-card h2 {
+ font-size: 26px;
+ padding-bottom: 16px;
+ }
+
+ .cn1-gs-step-row {
+ grid-template-columns: 1fr;
+ gap: 20px;
+ padding: 24px 0 28px;
+ }
+
+ .cn1-gs-step-row p {
+ font-size: 16px;
+ }
+
+ .cn1-gs-cta {
+ font-size: 16px;
+ padding: 12px 20px;
+ }
+
+ .cn1-gs-build-grid {
+ grid-template-columns: 1fr;
+ gap: 28px;
+ padding-top: 24px;
+ }
+
+ .cn1-gs-build-copy p {
+ font-size: 22px;
+ }
+
+ .cn1-gs-resource-card p {
+ font-size: 15px;
+ }
+
+ .cn1-gs-resource-card > a,
+ .cn1-gs-links a {
+ font-size: 15px;
+ }
+
+ .cn1-gs-hero__art img {
+ max-width: 360px;
+ }
+}
+
+@media (max-width: 768px) {
+ .cn1-gs-hero__art img {
+ max-width: 280px;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-home.css b/docs/website/assets/css/extended/cn1-home.css
new file mode 100644
index 0000000000..ec03306f2e
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-home.css
@@ -0,0 +1,1987 @@
+:root,
+body {
+ --cn1-home-bg: #f1f4fc;
+ --cn1-home-text: #0d1847;
+ --cn1-home-muted: #55607f;
+ --cn1-home-header-bg: #eef1f7;
+ --cn1-home-header-border: rgba(17, 33, 84, 0.1);
+ --cn1-home-strip-bg: #1d5ff0;
+ --cn1-home-strip-text: #eef4ff;
+ --cn1-home-nav-text: #0d1847;
+ --cn1-home-submenu-bg: #dbe3f7;
+ --cn1-home-submenu-border: #c3cde6;
+ --cn1-home-submenu-text: #0d1847;
+ --cn1-home-dashboard-border: #2f56d8;
+ --cn1-home-card-bg: #e7ecfb;
+ --cn1-home-card-border: #2b66ea;
+ --cn1-home-card-text: #1b274e;
+ --cn1-home-section-title: #2856dc;
+ --cn1-home-footer-bg: #f1f4fc;
+ --cn1-home-footer-text: #55607f;
+}
+
+body.cn1-homepage {
+ --cn1-home-bg: #f1f4fc;
+ --cn1-home-text: #0d1847;
+ --cn1-home-muted: #55607f;
+ --cn1-home-header-bg: #eef1f7;
+ --cn1-home-header-border: rgba(17, 33, 84, 0.1);
+ --cn1-home-strip-bg: #1d5ff0;
+ --cn1-home-strip-text: #eef4ff;
+ --cn1-home-nav-text: #0d1847;
+ --cn1-home-submenu-bg: #dbe3f7;
+ --cn1-home-submenu-border: #c3cde6;
+ --cn1-home-submenu-text: #0d1847;
+ --cn1-home-dashboard-border: #2f56d8;
+ --cn1-home-card-bg: #e7ecfb;
+ --cn1-home-card-border: #2b66ea;
+ --cn1-home-card-text: #1b274e;
+ --cn1-home-section-title: #2856dc;
+ --cn1-home-footer-bg: #f1f4fc;
+ --cn1-home-footer-text: #55607f;
+}
+
+body.dark,
+body.dark.cn1-homepage {
+ --cn1-home-bg: #020d44;
+ --cn1-home-text: #ffffff;
+ --cn1-home-muted: #dbe4ff;
+ --cn1-home-header-bg: #020d44;
+ --cn1-home-header-border: rgba(255, 255, 255, 0.08);
+ --cn1-home-strip-bg: #1062df;
+ --cn1-home-strip-text: #e8f0ff;
+ --cn1-home-nav-text: #ffffff;
+ --cn1-home-submenu-bg: #c8cedd;
+ --cn1-home-submenu-border: #b1b9cc;
+ --cn1-home-submenu-text: #0d1847;
+ --cn1-home-dashboard-border: #d9e3ff;
+ --cn1-home-card-bg: #374261;
+ --cn1-home-card-border: #1f6dff;
+ --cn1-home-card-text: #dbe4ff;
+ --cn1-home-section-title: #a8beff;
+ --cn1-home-footer-bg: #020d44;
+ --cn1-home-footer-text: #9eb5ff;
+}
+
+.cn1-homepage {
+ background: var(--cn1-home-bg);
+ color: var(--cn1-home-text);
+ font-family: "Poppins", sans-serif;
+}
+
+.cn1-homepage .main {
+ max-width: 100%;
+ padding: 0;
+}
+
+.cn1-homepage .header {
+ position: sticky;
+ top: 0;
+ z-index: 50;
+ background: var(--cn1-home-header-bg);
+ border-bottom: 1px solid var(--cn1-home-header-border);
+ padding-top: 40px;
+}
+
+.cn1-homepage .header::before {
+ content: "Open Source & Free";
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 40px;
+ background: var(--cn1-home-strip-bg);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--cn1-home-strip-text);
+}
+
+.cn1-homepage .nav {
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ max-width: 1320px;
+ min-height: 84px;
+ line-height: 84px;
+}
+
+.cn1-homepage .logo a {
+ position: relative;
+ color: #fff;
+ font-size: 0;
+ letter-spacing: 0;
+ font-weight: 700;
+ padding-left: 0;
+ display: flex;
+ align-items: center;
+}
+
+.cn1-homepage .logo a::before {
+ content: none;
+}
+
+.cn1-homepage .logo a img,
+.cn1-homepage .logo a svg {
+ height: 34px;
+ width: auto;
+ transform: none;
+ margin-inline-end: 0;
+ border-radius: 0;
+}
+
+.cn1-brand-logo {
+ height: 34px;
+ width: auto;
+ display: block;
+}
+
+body:not(.dark) .cn1-brand-logo {
+ /* Make the white header logo visible on light header backgrounds */
+ filter: brightness(0) saturate(100%);
+}
+
+.cn1-homepage #menu a {
+ color: var(--cn1-home-nav-text);
+ font-size: 18px;
+ font-weight: 700;
+ letter-spacing: 0.01em;
+}
+
+.cn1-homepage #menu {
+ display: flex;
+ align-items: center;
+ gap: 34px;
+ margin: 0;
+ margin-inline-start: auto;
+ overflow-x: visible;
+ scrollbar-width: none;
+}
+
+.cn1-homepage #menu::-webkit-scrollbar {
+ display: none;
+}
+
+.cn1-homepage #menu > li {
+ position: relative;
+}
+
+.cn1-homepage #menu > li + li {
+ margin-inline-start: 0;
+}
+
+.cn1-homepage #menu > li > a,
+.cn1-homepage #menu > li > details > summary {
+ color: var(--cn1-home-nav-text);
+ font-size: 18px;
+ font-weight: 700;
+ letter-spacing: 0.01em;
+ text-decoration: none;
+ line-height: 84px;
+}
+
+.cn1-homepage #menu > li > details > summary {
+ list-style: none;
+ cursor: pointer;
+}
+
+.cn1-homepage #menu > li > details > summary::-webkit-details-marker {
+ display: none;
+}
+
+.cn1-homepage #menu > li > details > summary::after {
+ content: " ▾";
+ font-size: 0.9em;
+}
+
+.cn1-homepage #menu > li > details[open] > summary {
+ color: #d4e1ff;
+}
+
+.cn1-homepage #menu .sub-menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ min-width: 300px;
+ background: var(--cn1-home-submenu-bg);
+ border-radius: 0 0 14px 14px;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.24);
+ position: absolute;
+ top: calc(100% - 2px);
+ left: 0;
+ z-index: 80;
+}
+
+.cn1-homepage #menu .sub-menu li + li {
+ border-top: 1px solid var(--cn1-home-submenu-border);
+}
+
+.cn1-homepage #menu .sub-menu a {
+ color: var(--cn1-home-submenu-text);
+ padding: 22px 26px;
+ line-height: 1.25;
+ font-size: 17px;
+ font-weight: 600;
+ display: block;
+}
+
+.cn1-homepage .cn1-nav-toggle {
+ display: none;
+}
+
+.cn1-homepage .cn1-nav-toggle-btn {
+ display: none;
+}
+
+.cn1-homepage .cn1-nav-links {
+ margin-inline-start: 0;
+}
+
+.cn1-homepage .cn1-dashboard-link {
+ border: 2px solid var(--cn1-home-dashboard-border);
+ border-radius: 12px;
+ padding: 0 28px;
+ line-height: 46px;
+ margin-top: 18px;
+ color: var(--cn1-home-nav-text);
+ font-size: 18px;
+ font-weight: 700;
+ text-decoration: none;
+ margin-inline-start: 24px;
+}
+
+.cn1-header {
+ position: sticky;
+ top: 0;
+ z-index: 50;
+ background: var(--cn1-home-header-bg);
+ border-bottom: 1px solid var(--cn1-home-header-border);
+ padding-top: 40px;
+}
+
+.cn1-header::before {
+ content: "Open Source & Free";
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 40px;
+ background: var(--cn1-home-strip-bg);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--cn1-home-strip-text);
+}
+
+.cn1-header .nav {
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ max-width: 1320px;
+ min-height: 84px;
+ line-height: 84px;
+ transition: min-height 180ms ease, line-height 180ms ease;
+}
+
+.cn1-header .logo a {
+ position: relative;
+ color: #fff;
+ font-size: 0;
+ letter-spacing: 0;
+ font-weight: 700;
+ padding-left: 0;
+ display: flex;
+ align-items: center;
+}
+
+.cn1-header .logo a img,
+.cn1-header .logo a svg {
+ height: 34px;
+ width: auto;
+ transform: none;
+ margin-inline-end: 0;
+ border-radius: 0;
+ transition: height 180ms ease;
+}
+
+.cn1-header #menu {
+ display: flex;
+ align-items: center;
+ gap: 34px;
+ margin: 0;
+ margin-inline-start: auto;
+ overflow-x: visible;
+ scrollbar-width: none;
+}
+
+.cn1-header #menu > li {
+ position: relative;
+}
+
+.cn1-header #menu > li + li {
+ margin-inline-start: 0;
+}
+
+.cn1-header #menu > li > a,
+.cn1-header #menu > li > .cn1-menu-trigger {
+ color: var(--cn1-home-nav-text);
+ font-size: 18px;
+ font-weight: 700;
+ letter-spacing: 0.01em;
+ text-decoration: none;
+ line-height: 84px;
+}
+
+.cn1-header #menu > li > .cn1-menu-trigger {
+ appearance: none;
+ background: none;
+ border: 0;
+ margin: 0;
+ padding: 0;
+ font: inherit;
+ font-size: 18px;
+ color: var(--cn1-home-nav-text);
+ cursor: pointer;
+}
+
+.cn1-header #menu > li.has-children > .cn1-menu-trigger::after {
+ content: " ▾";
+ font-size: 0.9em;
+}
+
+.cn1-header #menu .sub-menu {
+ display: none;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ min-width: 300px;
+ background: var(--cn1-home-submenu-bg);
+ border-radius: 0 0 14px 14px;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.24);
+ position: absolute;
+ top: calc(100% - 2px);
+ left: 0;
+ z-index: 80;
+}
+
+.cn1-header #menu .sub-menu li + li {
+ border-top: 1px solid var(--cn1-home-submenu-border);
+}
+
+.cn1-header #menu .sub-menu a {
+ color: var(--cn1-home-submenu-text);
+ padding: 22px 26px;
+ line-height: 1.25;
+ font-size: 17px;
+ font-weight: 600;
+ display: block;
+}
+
+.cn1-header #menu > li.has-children:hover > .sub-menu,
+.cn1-header #menu > li.has-children:focus-within > .sub-menu {
+ display: block;
+}
+
+.cn1-header .cn1-nav-toggle {
+ display: none;
+}
+
+.cn1-header .cn1-nav-toggle-btn {
+ display: none;
+}
+
+.cn1-header .cn1-nav-links {
+ margin-inline-start: 0;
+}
+
+.cn1-header .cn1-dashboard-link {
+ border: 2px solid var(--cn1-home-dashboard-border);
+ border-radius: 12px;
+ padding: 0 28px;
+ line-height: 46px;
+ margin-top: 18px;
+ color: var(--cn1-home-nav-text);
+ font-size: 18px;
+ font-weight: 700;
+ text-decoration: none;
+ margin-inline-start: 24px;
+ transition: line-height 180ms ease, font-size 180ms ease, margin-top 180ms ease, padding 180ms ease;
+}
+
+.cn1-theme-controls {
+ display: inline-flex;
+ align-items: center;
+ gap: 0;
+ margin-inline-start: 14px;
+}
+
+.cn1-theme-toggle-btn {
+ appearance: none;
+ border: 0;
+ background: transparent;
+ color: var(--cn1-home-nav-text);
+ border-radius: 0;
+ padding: 4px 6px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20px;
+ cursor: pointer;
+ line-height: 1;
+}
+
+.cn1-theme-toggle-btn:hover {
+ color: #8fb1ff;
+}
+
+.cn1-header.cn1-header--compact .nav {
+ min-height: 66px;
+ line-height: 66px;
+}
+
+.cn1-header.cn1-header--compact .logo a img,
+.cn1-header.cn1-header--compact .logo a svg {
+ height: 28px;
+}
+
+.cn1-header.cn1-header--compact #menu > li > a,
+.cn1-header.cn1-header--compact #menu > li > .cn1-menu-trigger {
+ line-height: 66px;
+}
+
+.cn1-header.cn1-header--compact .cn1-dashboard-link {
+ margin-top: 10px;
+ line-height: 40px;
+ font-size: 16px;
+ padding: 0 22px;
+}
+
+.cn1-hero,
+.cn1-band,
+.cn1-cards,
+.cn1-why,
+.cn1-home-v1-link {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding-left: 48px;
+ padding-right: 48px;
+}
+
+.cn1-hero {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 44px;
+ align-items: center;
+ padding-top: 64px;
+ padding-bottom: 64px;
+}
+
+.cn1-hero__eyebrow {
+ color: #cfdcff;
+ font-size: 15px;
+ font-weight: 700;
+ letter-spacing: 0.01em;
+}
+
+.cn1-hero__title {
+ margin-top: 20px;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 40px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 1.07;
+ color: #f5f8ff;
+ max-width: 640px;
+}
+
+.cn1-hero__platforms {
+ margin-top: 30px;
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.cn1-hero__platforms i {
+ color: #b6c7ff;
+ font-size: 34px;
+ line-height: 1;
+}
+
+.cn1-hero__copy {
+ margin-top: 30px;
+ color: #fff;
+ font-family: "Poppins", sans-serif;
+ font-size: 17px;
+ font-weight: 500;
+ line-height: 1.3em;
+ max-width: 640px;
+}
+
+.cn1-hero__trust {
+ margin-top: 26px;
+ font-size: 20px;
+ color: #d0dcff;
+ font-weight: 600;
+}
+
+.cn1-hero__actions {
+ margin-top: 36px;
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.cn1-btn {
+ display: inline-block;
+ font-size: 17px;
+ font-weight: 700;
+ padding: 12px 34px;
+ border-radius: 8px;
+}
+
+.cn1-btn--primary {
+ border: 2px solid #d8e5ff;
+ color: #fff;
+}
+
+.cn1-btn--ghost {
+ background: #fff;
+ color: #1a1f2f;
+ border-radius: 10px;
+ font-size: 20px;
+ padding: 8px 16px;
+}
+
+.cn1-hero__art img {
+ width: 100%;
+ max-width: 680px;
+ margin-left: auto;
+}
+
+.cn1-band {
+ text-align: center;
+ padding-top: 34px;
+ padding-bottom: 30px;
+}
+
+.cn1-band h2 {
+ color: #a8beff;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 35px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-variant-alternates: normal;
+ font-variant-caps: normal;
+ font-variant-east-asian: normal;
+ font-variant-emoji: normal;
+ font-variant-ligatures: normal;
+ font-variant-numeric: normal;
+ font-variant-position: normal;
+ font-variation-settings: normal;
+ font-weight: 500;
+ line-height: 1.2;
+}
+
+.cn1-band h2 span {
+ margin-left: 20px;
+}
+
+.cn1-band__icons {
+ display: inline-flex;
+ gap: 14px;
+ margin-left: 18px;
+ vertical-align: middle;
+}
+
+.cn1-band__icons i {
+ font-size: 36px;
+ color: #a8beff;
+}
+
+.cn1-cards {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 30px;
+ padding-top: 26px;
+ padding-bottom: 48px;
+}
+
+.cn1-card {
+ background: #374261;
+ border: 2px solid #1f6dff;
+ border-radius: 12px;
+ padding: 30px 28px;
+ min-height: 300px;
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.cn1-card::before {
+ font-family: "Font Awesome 5 Free";
+ font-weight: 900;
+ content: "\f1e0";
+ font-size: 52px;
+ line-height: 1;
+ color: #a8beff;
+ margin-bottom: 14px;
+}
+
+.cn1-card:nth-child(2)::before {
+ content: "\f7d9";
+}
+
+.cn1-card:nth-child(3)::before {
+ content: "\f1fc";
+}
+
+.cn1-card h3 {
+ color: #edf2ff;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 20px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-variant-alternates: normal;
+ font-variant-caps: normal;
+ font-variant-east-asian: normal;
+ font-variant-emoji: normal;
+ font-variant-ligatures: normal;
+ font-variant-numeric: normal;
+ font-variant-position: normal;
+ font-variation-settings: normal;
+ font-weight: 500;
+ line-height: 1.2;
+ margin-bottom: 14px;
+}
+
+.cn1-card p {
+ color: #dbe4ff;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 14px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-variant-alternates: normal;
+ font-variant-caps: normal;
+ font-variant-east-asian: normal;
+ font-variant-emoji: normal;
+ font-variant-ligatures: normal;
+ font-variant-numeric: normal;
+ font-variant-position: normal;
+ font-variation-settings: normal;
+ font-weight: 300;
+ line-height: 1.35;
+}
+
+.cn1-card a {
+ color: #f5f8ff;
+ text-decoration: underline;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 12px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-variant-alternates: normal;
+ font-variant-caps: normal;
+ font-variant-east-asian: normal;
+ font-variant-emoji: normal;
+ font-variant-ligatures: normal;
+ font-variant-numeric: normal;
+ font-variant-position: normal;
+ font-variation-settings: normal;
+ font-weight: 500;
+ line-height: 12px;
+ white-space: nowrap;
+ overflow-wrap: break-word;
+ text-wrap-mode: nowrap;
+}
+
+.cn1-card__links {
+ display: flex;
+ gap: 18px;
+ flex-wrap: wrap;
+ margin-top: auto;
+ justify-content: center;
+}
+
+.cn1-card > a {
+ margin-top: auto;
+}
+
+.cn1-card__links a {
+ margin-top: 0;
+}
+
+.cn1-why {
+ padding-top: 26px;
+ padding-bottom: 86px;
+}
+
+.cn1-why h2 {
+ text-align: center;
+ color: #a8beff;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 35px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-variation-settings: normal;
+ font-weight: 500;
+}
+
+.cn1-why__grid {
+ margin-top: 36px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 28px 36px;
+}
+
+.cn1-why__grid article h3 {
+ color: #e9efff;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 20px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-variation-settings: normal;
+ font-weight: 500;
+ margin-bottom: 10px;
+}
+
+.cn1-why__grid article p {
+ color: #dbe4ff;
+ font-family: "Poppins", sans-serif;
+ font-feature-settings: normal;
+ font-kerning: auto;
+ font-language-override: normal;
+ font-optical-sizing: auto;
+ font-size: 14px;
+ font-size-adjust: none;
+ font-stretch: 100%;
+ font-style: normal;
+ font-variation-settings: normal;
+ font-weight: 300;
+ line-height: 1.35;
+}
+
+.cn1-why__grid article i {
+ display: inline-block;
+ color: #a8beff;
+ font-size: 44px;
+ line-height: 1;
+ margin-bottom: 18px;
+}
+
+.cn1-why__cta {
+ margin-top: 34px;
+ text-align: center;
+}
+
+.cn1-why__cta a {
+ color: #f5f8ff;
+ text-decoration: underline;
+ font-family: "Poppins", sans-serif;
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 12px;
+ white-space: nowrap;
+}
+
+.cn1-home-v1-link {
+ padding-top: 0;
+ padding-bottom: 40px;
+ text-align: center;
+}
+
+.cn1-home-v1-link a {
+ color: #a7c0ff;
+ text-decoration: underline;
+ font-size: 22px;
+}
+
+.footer,
+.cn1-site-footer {
+ background: var(--cn1-home-footer-bg);
+ color: var(--cn1-home-footer-text);
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.cn1-site-footer {
+ max-width: none;
+ margin: 0;
+ padding: 0;
+ text-align: left;
+ line-height: 1.4;
+}
+
+.cn1-how,
+.cn1-made,
+.cn1-loved,
+.cn1-blog,
+.cn1-cta-row,
+.cn1-newsletter {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding-left: 48px;
+ padding-right: 48px;
+}
+
+.cn1-how,
+.cn1-made,
+.cn1-loved,
+.cn1-blog,
+.cn1-newsletter {
+ padding-top: 64px;
+}
+
+.cn1-how h2,
+.cn1-made h2,
+.cn1-loved h2,
+.cn1-blog h2,
+.cn1-newsletter h2 {
+ text-align: center;
+ color: #a8beff;
+ font-family: "Poppins", sans-serif;
+ font-size: 35px;
+ font-weight: 500;
+}
+
+.cn1-how__intro {
+ max-width: 780px;
+ margin: 18px auto 0;
+ color: #dbe4ff;
+ text-align: center;
+ font-size: 14px;
+ font-weight: 300;
+}
+
+.cn1-how__layout {
+ margin-top: 42px;
+ display: grid;
+ grid-template-columns: 1fr 1.8fr 1fr;
+ gap: 20px;
+ align-items: start;
+}
+
+.cn1-how__list h3 {
+ text-align: center;
+ color: #a8beff;
+ font-size: 20px;
+ margin-bottom: 16px;
+}
+
+.cn1-accordion {
+ display: grid;
+ gap: 10px;
+}
+
+.cn1-acc-item {
+ background: #374261;
+ border: 1px solid #465270;
+ overflow: hidden;
+}
+
+.cn1-acc-trigger {
+ width: 100%;
+ border: 0;
+ background: transparent;
+ color: #f5f8ff;
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ align-items: center;
+ gap: 12px;
+ text-align: left;
+ padding: 14px 16px;
+ font-size: 20px;
+ font-weight: 500;
+ cursor: pointer;
+}
+
+.cn1-acc-icon {
+ width: 20px;
+ text-align: center;
+}
+
+.cn1-acc-icon--ok {
+ color: #96dc53;
+}
+
+.cn1-acc-icon--no {
+ color: #ff486e;
+}
+
+.cn1-acc-icon--q {
+ color: #5e86ff;
+}
+
+.cn1-acc-panel {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 260ms ease;
+ background: #c8cedd;
+}
+
+.cn1-acc-panel p {
+ margin: 0;
+ padding: 16px 24px;
+ color: #0d1847;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.35;
+}
+
+.cn1-acc-caret {
+ color: #f5f8ff;
+ text-align: right;
+}
+
+.cn1-how__diagram img {
+ width: 100%;
+ border-radius: 8px;
+}
+
+.cn1-carousel {
+ margin-top: 34px;
+ position: relative;
+}
+
+.cn1-carousel__viewport {
+ overflow: hidden;
+}
+
+.cn1-carousel__track {
+ display: flex;
+ transition: transform 320ms ease;
+}
+
+.cn1-carousel__slide {
+ min-width: 100%;
+ box-sizing: border-box;
+}
+
+.cn1-carousel__prev,
+.cn1-carousel__next {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ border: 0;
+ background: none;
+ color: #a8beff;
+ font-size: 42px;
+ cursor: pointer;
+}
+
+.cn1-carousel__prev {
+ left: -40px;
+}
+
+.cn1-carousel__next {
+ right: -40px;
+}
+
+.cn1-carousel__dots {
+ margin-top: 16px;
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+}
+
+.cn1-carousel__dots button {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ border: 0;
+ background: rgba(168, 190, 255, 0.35);
+ cursor: pointer;
+}
+
+.cn1-carousel__dots button.is-active {
+ background: #a8beff;
+}
+
+.cn1-made .cn1-carousel__slide {
+ text-align: center;
+ max-width: 760px;
+ margin: 0 auto;
+}
+
+.cn1-made .cn1-carousel__slide img {
+ width: 370px;
+ max-width: 100%;
+ display: block;
+ margin: 0 auto;
+}
+
+.cn1-made .cn1-carousel__slide h3 {
+ margin-top: 14px;
+ color: #dbe4ff;
+ font-size: 20px;
+ font-weight: 500;
+}
+
+.cn1-made .cn1-carousel__slide p {
+ color: #dbe4ff;
+ font-size: 14px;
+ font-weight: 300;
+}
+
+.cn1-made .cn1-carousel__slide a {
+ color: #a8beff;
+ font-size: 22px;
+}
+
+.cn1-made .cn1-carousel {
+ max-width: 920px;
+ margin: 34px auto 0;
+}
+
+.cn1-made .cn1-carousel__prev {
+ left: -12px;
+}
+
+.cn1-made .cn1-carousel__next {
+ right: -12px;
+}
+
+.cn1-loved__grid {
+ margin-top: 32px;
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr;
+ gap: 26px;
+ align-items: center;
+}
+
+.cn1-loved__grid > img {
+ width: 100%;
+ opacity: 0.95;
+ filter: brightness(1.12) contrast(1.1);
+}
+
+.cn1-loved__grid .cn1-carousel {
+ margin-top: 0;
+}
+
+.cn1-loved__grid .cn1-carousel__prev,
+.cn1-loved__grid .cn1-carousel__next {
+ display: none;
+}
+
+.cn1-loved__grid .cn1-carousel__slide {
+ text-align: center;
+}
+
+.cn1-loved__grid .cn1-carousel__slide blockquote {
+ position: relative;
+ border: 2px solid #1f6dff;
+ border-radius: 8px;
+ margin: 0 auto;
+ padding: 24px 28px;
+ color: #f5f8ff;
+ font-size: 20px;
+ line-height: 1.35;
+ font-weight: 600;
+ max-width: 560px;
+}
+
+.cn1-loved__grid .cn1-carousel__slide blockquote span {
+ display: block;
+}
+
+.cn1-loved__grid .cn1-carousel__slide blockquote span + span {
+ margin-top: 12px;
+ color: #a8beff;
+ font-style: italic;
+ font-weight: 500;
+}
+
+.cn1-loved__grid .cn1-carousel__slide blockquote::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: -9px;
+ width: 16px;
+ height: 16px;
+ background: #1b1e27;
+ border-right: 2px solid #1f6dff;
+ border-bottom: 2px solid #1f6dff;
+ transform: translateX(-50%) rotate(45deg);
+}
+
+.cn1-loved__grid .cn1-carousel__slide p {
+ color: #a8beff;
+ font-size: 14px;
+ font-weight: 300;
+}
+
+.cn1-loved__grid .cn1-carousel__slide img {
+ width: 54px;
+ height: 54px;
+ border-radius: 50%;
+ margin: 20px auto 0;
+}
+
+.cn1-loved__grid .cn1-carousel__slide h3 {
+ color: #f5f8ff;
+ font-size: 20px;
+ font-weight: 500;
+ margin: 12px 0 6px;
+}
+
+.cn1-loved__grid .cn1-carousel__slide h3 + p {
+ max-width: 620px;
+ margin: 0 auto;
+ font-weight: 500;
+}
+
+.cn1-blog__grid {
+ margin-top: 0;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 24px;
+}
+
+.cn1-blog .cn1-carousel__prev,
+.cn1-blog .cn1-carousel__next {
+ display: none;
+}
+
+.cn1-blog__grid article {
+ background: #374261;
+ border: 1px solid #465270;
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+.cn1-blog__grid article img {
+ width: 100%;
+ display: block;
+ aspect-ratio: 16 / 10;
+ object-fit: cover;
+}
+
+.cn1-blog__grid article > div {
+ padding: 14px 16px;
+}
+
+.cn1-blog__grid h3,
+.cn1-blog__grid h3 a {
+ color: #f5f8ff;
+ font-size: 20px;
+ font-weight: 500;
+ text-decoration: none;
+ text-align: center;
+}
+
+.cn1-blog__grid p {
+ margin: 10px 0 0;
+ color: #8d98b8;
+ font-size: 12px;
+ text-align: center;
+}
+
+.cn1-cta-row {
+ margin-top: 56px;
+ border: 1px solid #1f6dff;
+ border-radius: 10px;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ overflow: hidden;
+ padding-left: 0;
+ padding-right: 0;
+ background: #374261;
+}
+
+.cn1-cta-row article {
+ background: #374261;
+ padding: 34px 40px;
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ align-items: center;
+ gap: 24px;
+}
+
+.cn1-cta-row article + article {
+ border-left: 1px solid #1f6dff;
+}
+
+.cn1-cta-row article i {
+ color: #a8beff;
+ font-size: 54px;
+}
+
+.cn1-cta-row article h3 {
+ color: #f5f8ff;
+ font-size: 20px;
+ font-weight: 500;
+ margin: 0;
+}
+
+.cn1-cta-row article a {
+ border: 2px solid #d8e5ff;
+ color: #f5f8ff;
+ padding: 10px 26px;
+ font-size: 20px;
+ font-weight: 500;
+ text-decoration: none;
+}
+
+.cn1-newsletter p {
+ text-align: center;
+ color: #dbe4ff;
+ font-size: 14px;
+}
+
+.cn1-newsletter form {
+ margin: 24px auto 0;
+ max-width: 1060px;
+ display: grid;
+ grid-template-columns: 1fr 1fr auto;
+ gap: 16px;
+}
+
+.cn1-newsletter input {
+ background: #c8cedd;
+ border: 0;
+ border-radius: 6px;
+ padding: 12px 16px;
+ font-size: 20px;
+}
+
+.cn1-newsletter button {
+ border: 2px solid #d8e5ff;
+ background: transparent;
+ color: #f5f8ff;
+ border-radius: 6px;
+ padding: 12px 28px;
+ font-size: 20px;
+}
+
+.cn1-bottom-links {
+ max-width: 1320px;
+ margin: 60px auto 0;
+ padding: 46px 48px 60px;
+ display: grid;
+ grid-template-columns: 1.1fr 1fr 0.6fr;
+ gap: 30px;
+ border-top: 1px solid rgba(255, 255, 255, 0.15);
+}
+
+.cn1-bottom-links__col h3 {
+ color: #afc1ff;
+ font-family: "Roboto", sans-serif;
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 26px;
+}
+
+.cn1-bottom-links__col ul {
+ columns: 2;
+ list-style: none;
+ padding: 0;
+ margin: 22px 0 0;
+}
+
+.cn1-bottom-links__col li {
+ margin: 0 0 12px;
+ break-inside: avoid;
+ position: relative;
+ padding-left: 18px;
+}
+
+.cn1-bottom-links__col li::before {
+ content: "\203A";
+ position: absolute;
+ left: 0;
+ top: 0;
+ color: #818698;
+ font-size: 20px;
+ line-height: 16px;
+}
+
+.cn1-bottom-links__col a {
+ color: #818698;
+ text-decoration: none;
+ font-family: "Poppins", sans-serif;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 16px;
+ padding-left: 5px;
+ transition: color 0.3s ease;
+}
+
+.cn1-bottom-links__col a:hover {
+ color: #a2aed0;
+}
+
+.cn1-bottom-links__brand {
+ text-align: center;
+ align-self: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.cn1-bottom-links__brand .cn1-footer-logo {
+ width: 205px;
+ max-width: 100%;
+}
+
+.cn1-footer-logo--light {
+ display: none;
+}
+
+body:not(.dark) .cn1-footer-logo--dark {
+ display: none;
+}
+
+body:not(.dark) .cn1-footer-logo--light {
+ display: block;
+}
+
+.cn1-bottom-links__brand p {
+ color: #a8beff;
+ font-family: "Poppins", sans-serif;
+ font-size: 17px;
+ font-weight: 500;
+ line-height: 1.2;
+ margin: 16px 0 0;
+}
+
+.cn1-bottom-links__stats {
+ text-align: right;
+ color: #afc1ff;
+ font-size: 13px;
+ align-self: center;
+}
+
+.cn1-bottom-links__stats p {
+ margin: 0 0 34px;
+}
+
+.cn1-bottom-links__stats span {
+ color: #afc1ff;
+ font-family: "Poppins", sans-serif;
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 13px;
+}
+
+.cn1-bottom-links__stats strong {
+ color: #eaeffd;
+ font-family: "Helvetica Neue", sans-serif;
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 22px;
+ display: block;
+}
+
+.cn1-footer-meta {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 0 48px 28px;
+ color: #818698;
+ display: flex;
+ justify-content: space-between;
+ gap: 24px;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.cn1-footer-meta p {
+ margin: 18px 0 0;
+ font-family: "Poppins", sans-serif;
+ font-size: 16px;
+ line-height: 1.35;
+}
+
+.cn1-footer-meta a {
+ color: #7ca9ff;
+ text-decoration: none;
+}
+
+.cn1-footer-meta a:hover {
+ color: #9fbeff;
+}
+
+body:not(.dark).cn1-homepage .cn1-hero__title,
+body:not(.dark).cn1-homepage .cn1-hero__copy,
+body:not(.dark).cn1-homepage .cn1-hero__trust,
+body:not(.dark).cn1-homepage .cn1-band h2,
+body:not(.dark).cn1-homepage .cn1-why h2,
+body:not(.dark).cn1-homepage .cn1-how h2,
+body:not(.dark).cn1-homepage .cn1-made h2,
+body:not(.dark).cn1-homepage .cn1-loved h2,
+body:not(.dark).cn1-homepage .cn1-blog h2,
+body:not(.dark).cn1-homepage .cn1-newsletter h2 {
+ color: #1e3fba;
+}
+
+body:not(.dark).cn1-homepage .cn1-hero__eyebrow {
+ color: #516084;
+}
+
+body:not(.dark).cn1-homepage .cn1-cards .cn1-card,
+body:not(.dark).cn1-homepage .cn1-cta-row article,
+body:not(.dark).cn1-homepage .cn1-blog__grid article {
+ background: var(--cn1-home-card-bg);
+ border-color: var(--cn1-home-card-border);
+}
+
+body:not(.dark).cn1-homepage .cn1-card h3,
+body:not(.dark).cn1-homepage .cn1-card p,
+body:not(.dark).cn1-homepage .cn1-card a,
+body:not(.dark).cn1-homepage .cn1-why__grid article h3,
+body:not(.dark).cn1-homepage .cn1-why__grid article p,
+body:not(.dark).cn1-homepage .cn1-how__intro,
+body:not(.dark).cn1-homepage .cn1-how__list h3,
+body:not(.dark).cn1-homepage .cn1-made .cn1-carousel__slide h3,
+body:not(.dark).cn1-homepage .cn1-made .cn1-carousel__slide p,
+body:not(.dark).cn1-homepage .cn1-loved__grid .cn1-carousel__slide h3,
+body:not(.dark).cn1-homepage .cn1-loved__grid .cn1-carousel__slide p,
+body:not(.dark).cn1-homepage .cn1-blog__grid h3,
+body:not(.dark).cn1-homepage .cn1-blog__grid h3 a,
+body:not(.dark).cn1-homepage .cn1-cta-row article h3 {
+ color: var(--cn1-home-card-text);
+}
+
+body:not(.dark).cn1-homepage .cn1-btn--primary,
+body:not(.dark).cn1-homepage .cn1-dashboard-link {
+ color: #173287;
+}
+
+body:not(.dark).cn1-homepage .cn1-bottom-links__col a,
+body:not(.dark).cn1-homepage .cn1-footer-meta p {
+ color: #65739a;
+}
+
+body:not(.dark).cn1-homepage .cn1-loved__grid .cn1-carousel__slide blockquote {
+ color: #1b274e;
+ background: #e7ecfb;
+ border-color: #2b66ea;
+}
+
+body:not(.dark).cn1-homepage .cn1-loved__grid .cn1-carousel__slide blockquote span + span {
+ color: #4a5f97;
+}
+
+body:not(.dark).cn1-homepage .cn1-loved__grid .cn1-carousel__slide blockquote::after {
+ background: #e7ecfb;
+ border-right-color: #2b66ea;
+ border-bottom-color: #2b66ea;
+}
+
+body:not(.dark).cn1-homepage .cn1-cta-row article a {
+ color: #173287;
+ border-color: #173287;
+}
+
+body:not(.dark).cn1-homepage .cn1-bottom-links__stats strong {
+ color: #1b274e;
+}
+
+body:not(.dark).cn1-homepage .cn1-bottom-links__stats span {
+ color: #4f648f;
+}
+
+body:not(.dark) .cn1-theme-toggle-btn {
+ color: #173287;
+}
+
+@media (max-width: 1400px) {
+ .cn1-homepage .header::before {
+ font-size: 18px;
+ }
+
+ .cn1-header::before {
+ font-size: 18px;
+ }
+
+ .cn1-homepage .logo a {
+ font-size: 0;
+ }
+
+ .cn1-homepage .logo a img,
+ .cn1-homepage .logo a svg {
+ height: 30px;
+ }
+
+ .cn1-header .logo a img,
+ .cn1-header .logo a svg {
+ height: 30px;
+ }
+
+ .cn1-brand-logo {
+ height: 30px;
+ }
+
+ .cn1-homepage #menu a {
+ font-size: 16px;
+ }
+
+ .cn1-homepage #menu > li > a,
+ .cn1-homepage #menu > li > details > summary {
+ font-size: 16px;
+ }
+
+ .cn1-header #menu > li > a,
+ .cn1-header #menu > li > .cn1-menu-trigger {
+ font-size: 16px;
+ }
+
+ .cn1-header #menu > li > .cn1-menu-trigger {
+ font-size: 16px;
+ }
+
+ .cn1-homepage .cn1-dashboard-link {
+ margin-top: 20px;
+ line-height: 44px;
+ font-size: 16px;
+ }
+
+ .cn1-header .cn1-dashboard-link {
+ margin-top: 20px;
+ line-height: 44px;
+ font-size: 16px;
+ }
+
+ .cn1-hero__eyebrow {
+ font-size: 14px;
+ }
+
+ .cn1-hero__title {
+ font-size: 40px;
+ }
+
+ .cn1-hero__copy {
+ font-size: 17px;
+ }
+
+ .cn1-hero__trust {
+ font-size: 18px;
+ }
+
+ .cn1-btn {
+ font-size: 16px;
+ }
+
+ .cn1-btn--ghost {
+ font-size: 18px;
+ }
+
+ .cn1-band h2,
+ .cn1-why h2 {
+ font-size: 35px;
+ }
+
+ .cn1-card h3,
+ .cn1-why__grid article h3 {
+ font-size: 20px;
+ }
+
+ .cn1-card p,
+ .cn1-why__grid article p {
+ font-size: 14px;
+ }
+
+ .cn1-card a,
+ .cn1-why__cta a {
+ font-size: 18px;
+ }
+
+ .cn1-how h2,
+ .cn1-made h2,
+ .cn1-loved h2,
+ .cn1-blog h2,
+ .cn1-newsletter h2 {
+ font-size: 30px;
+ }
+}
+
+@media (max-width: 980px) {
+ .cn1-homepage .header {
+ position: static;
+ padding-top: 34px;
+ }
+
+ .cn1-header {
+ position: static;
+ padding-top: 34px;
+ }
+
+ .cn1-homepage .nav {
+ flex-wrap: wrap;
+ min-height: 64px;
+ line-height: 1.2;
+ padding: 16px 0;
+ }
+
+ .cn1-header .nav {
+ flex-wrap: wrap;
+ min-height: 64px;
+ line-height: 1.2;
+ padding: 16px 0;
+ }
+
+ .cn1-homepage .logo a {
+ font-size: 0;
+ padding-left: 0;
+ }
+
+ .cn1-homepage .logo a::before {
+ content: none;
+ }
+
+ .cn1-homepage .logo a img,
+ .cn1-homepage .logo a svg {
+ height: 26px;
+ }
+
+ .cn1-header .logo a img,
+ .cn1-header .logo a svg {
+ height: 26px;
+ }
+
+ .cn1-brand-logo {
+ height: 26px;
+ }
+
+ .cn1-homepage .logo,
+ .cn1-homepage #menu {
+ margin: 0 16px;
+ }
+
+ .cn1-homepage .cn1-nav-toggle-btn {
+ margin-left: auto;
+ margin-right: 14px;
+ width: 42px;
+ height: 42px;
+ border: 2px solid #d9e3ff;
+ border-radius: 10px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 21px;
+ line-height: 1;
+ cursor: pointer;
+ }
+
+ .cn1-header .cn1-nav-toggle-btn {
+ margin-left: auto;
+ margin-right: 14px;
+ width: 42px;
+ height: 42px;
+ border: 2px solid #d9e3ff;
+ border-radius: 10px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 21px;
+ line-height: 1;
+ cursor: pointer;
+ }
+
+ .cn1-homepage .cn1-nav-toggle-btn .fa-times {
+ display: none;
+ }
+
+ .cn1-header .cn1-nav-toggle-btn .fa-times {
+ display: none;
+ }
+
+ .cn1-homepage .cn1-dashboard-link {
+ margin: 0 16px 0 0;
+ line-height: 36px;
+ padding: 0 16px;
+ font-size: 14px;
+ }
+
+ .cn1-header .cn1-dashboard-link {
+ margin: 0 16px 0 0;
+ line-height: 36px;
+ padding: 0 16px;
+ font-size: 14px;
+ }
+
+ .cn1-homepage .cn1-nav-links {
+ display: none;
+ width: 100%;
+ margin: 14px 0 0 0;
+ background: #c8cedd;
+ border-radius: 0 0 12px 12px;
+ overflow: hidden;
+ }
+
+ .cn1-header .cn1-nav-links {
+ display: none;
+ width: 100%;
+ margin: 14px 0 0 0;
+ background: #c8cedd;
+ border-radius: 0 0 12px 12px;
+ overflow: hidden;
+ }
+
+ .cn1-homepage .cn1-nav-toggle:checked + .cn1-nav-toggle-btn .fa-bars {
+ display: none;
+ }
+
+ .cn1-header .cn1-nav-toggle:checked + .cn1-nav-toggle-btn .fa-bars {
+ display: none;
+ }
+
+ .cn1-homepage .cn1-nav-toggle:checked + .cn1-nav-toggle-btn .fa-times {
+ display: inline-block;
+ }
+
+ .cn1-header .cn1-nav-toggle:checked + .cn1-nav-toggle-btn .fa-times {
+ display: inline-block;
+ }
+
+ .cn1-homepage .cn1-nav-toggle:checked ~ .cn1-nav-links {
+ display: block;
+ }
+
+ .cn1-header .cn1-nav-toggle:checked ~ .cn1-nav-links {
+ display: block;
+ }
+
+ .cn1-homepage #menu {
+ display: block;
+ margin: 0;
+ width: 100%;
+ }
+
+ .cn1-header #menu {
+ display: block;
+ margin: 0;
+ width: 100%;
+ }
+
+ .cn1-homepage #menu li + li {
+ margin-inline-start: 0;
+ border-top: 1px solid #b1b9cc;
+ }
+
+ .cn1-header #menu li + li {
+ margin-inline-start: 0;
+ border-top: 1px solid #b1b9cc;
+ }
+
+ .cn1-homepage #menu a {
+ color: #0d1847;
+ font-size: 16px;
+ padding: 16px 22px;
+ line-height: 1.25;
+ }
+
+ .cn1-homepage #menu > li > a,
+ .cn1-homepage #menu > li > details > summary {
+ color: #0d1847;
+ line-height: 1.25;
+ display: block;
+ padding: 16px 22px;
+ font-size: 16px;
+ }
+
+ .cn1-header #menu > li > a,
+ .cn1-header #menu > li > .cn1-menu-trigger {
+ color: #0d1847;
+ line-height: 1.25;
+ display: block;
+ padding: 16px 22px;
+ font-size: 16px;
+ text-align: left;
+ width: 100%;
+ }
+
+ .cn1-homepage #menu > li > details > summary::after,
+ .cn1-header #menu > li > .cn1-menu-trigger::after {
+ float: right;
+ }
+
+ .cn1-homepage #menu .sub-menu {
+ position: static;
+ min-width: 0;
+ border-radius: 0;
+ box-shadow: none;
+ background: #d8ddea;
+ }
+
+ .cn1-header #menu .sub-menu {
+ display: none;
+ position: static;
+ min-width: 0;
+ border-radius: 0;
+ box-shadow: none;
+ background: #d8ddea;
+ }
+
+ .cn1-homepage #menu .sub-menu a {
+ padding: 15px 28px;
+ font-size: 15px;
+ }
+
+ .cn1-header #menu .sub-menu a {
+ padding: 15px 28px;
+ font-size: 15px;
+ }
+
+ .cn1-header #menu > li.has-children.open > .sub-menu {
+ display: block !important;
+ }
+
+ .cn1-theme-controls {
+ width: 100%;
+ justify-content: center;
+ padding: 10px 12px 4px;
+ border-top: 1px solid #b1b9cc;
+ margin-inline-start: 0;
+ background: #d8ddea;
+ }
+
+ .cn1-theme-toggle-btn {
+ color: #173287;
+ font-size: 22px;
+ }
+
+ .cn1-hero,
+ .cn1-band,
+ .cn1-cards,
+ .cn1-why,
+ .cn1-home-v1-link,
+ .cn1-how,
+ .cn1-made,
+ .cn1-loved,
+ .cn1-blog,
+ .cn1-cta-row,
+ .cn1-newsletter {
+ padding-left: 18px;
+ padding-right: 18px;
+ }
+
+ .cn1-hero {
+ grid-template-columns: 1fr;
+ padding-top: 32px;
+ }
+
+ .cn1-hero__title {
+ font-size: 44px;
+ }
+
+ .cn1-hero__platforms i {
+ font-size: 28px;
+ }
+
+ .cn1-hero__copy {
+ font-size: 18px;
+ }
+
+ .cn1-hero__trust {
+ font-size: 18px;
+ }
+
+ .cn1-btn {
+ font-size: 16px;
+ }
+
+ .cn1-band h2 {
+ font-size: 36px;
+ line-height: 1.2;
+ }
+
+ .cn1-band h2 span {
+ margin-left: 0;
+ }
+
+ .cn1-band__icons {
+ display: none;
+ }
+
+ .cn1-cards,
+ .cn1-why__grid {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-card {
+ min-height: 0;
+ }
+
+ .cn1-why h2 {
+ font-size: 40px;
+ }
+
+ .cn1-how__layout,
+ .cn1-loved__grid,
+ .cn1-blog__grid,
+ .cn1-cta-row,
+ .cn1-newsletter form {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-cta-row article + article {
+ border-left: 0;
+ border-top: 1px solid #1f6dff;
+ }
+
+ .cn1-bottom-links__col ul {
+ columns: 1;
+ }
+
+ .cn1-bottom-links__stats,
+ .cn1-bottom-links__brand {
+ text-align: left;
+ }
+
+ .cn1-bottom-links__brand .cn1-footer-logo {
+ display: none;
+ }
+
+ .cn1-bottom-links {
+ padding: 34px 18px 40px;
+ }
+
+ .cn1-footer-meta {
+ padding: 0 18px 20px;
+ flex-direction: column;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-howdoi.css b/docs/website/assets/css/extended/cn1-howdoi.css
new file mode 100644
index 0000000000..64d3ef6518
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-howdoi.css
@@ -0,0 +1,256 @@
+.cn1-howdoi-index {
+ --cn1-howdoi-bg: #eef2fb;
+ --cn1-howdoi-card: #ffffff;
+ --cn1-howdoi-border: #d6e0f6;
+ --cn1-howdoi-title: #102154;
+ --cn1-howdoi-text: #4b5f8f;
+ --cn1-howdoi-accent: #1d52ff;
+ --cn1-howdoi-chip: #edf2ff;
+ --cn1-howdoi-shadow: 0 18px 32px -24px rgba(16, 33, 84, 0.35);
+ background: var(--cn1-howdoi-bg);
+ margin: 0 calc(50% - 50vw);
+ padding: 2.5rem 0 4rem;
+}
+
+body.dark .cn1-howdoi-index {
+ --cn1-howdoi-bg: #06103a;
+ --cn1-howdoi-card: #131f54;
+ --cn1-howdoi-border: #31438c;
+ --cn1-howdoi-title: #eff4ff;
+ --cn1-howdoi-text: #b7c9ff;
+ --cn1-howdoi-accent: #95b8ff;
+ --cn1-howdoi-chip: rgba(255, 255, 255, 0.1);
+ --cn1-howdoi-shadow: 0 18px 32px -24px rgba(4, 9, 28, 0.72);
+}
+
+.cn1-howdoi-index .post-header,
+.cn1-howdoi-grid {
+ max-width: 1240px;
+ margin-inline: auto;
+ padding-inline: 1.25rem;
+}
+
+.cn1-howdoi-index .post-header {
+ margin-bottom: 1.4rem;
+}
+
+.cn1-howdoi-index__eyebrow {
+ margin: 0 0 0.6rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ font-size: 0.78rem;
+ font-weight: 700;
+ color: var(--cn1-howdoi-accent);
+}
+
+.cn1-howdoi-index .post-title {
+ margin: 0;
+ color: var(--cn1-howdoi-title);
+}
+
+.cn1-howdoi-index .post-description,
+.cn1-howdoi-index .content {
+ color: var(--cn1-howdoi-text);
+}
+
+.cn1-howdoi-index__count {
+ margin: 0.8rem 0 0;
+ font-weight: 600;
+ color: var(--cn1-howdoi-accent);
+}
+
+.cn1-howdoi-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 1rem;
+}
+
+.cn1-howdoi-card {
+ border: 1px solid var(--cn1-howdoi-border);
+ background: var(--cn1-howdoi-card);
+ border-radius: 16px;
+ overflow: hidden;
+ box-shadow: var(--cn1-howdoi-shadow);
+}
+
+.cn1-howdoi-card__thumb {
+ position: relative;
+ display: block;
+ aspect-ratio: 16 / 9;
+ overflow: hidden;
+}
+
+.cn1-howdoi-card__thumb img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.25s ease;
+}
+
+.cn1-howdoi-card:hover .cn1-howdoi-card__thumb img {
+ transform: scale(1.03);
+}
+
+.cn1-howdoi-card__play {
+ position: absolute;
+ right: 0.85rem;
+ bottom: 0.85rem;
+ width: 2rem;
+ height: 2rem;
+ border-radius: 999px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.8rem;
+ background: rgba(15, 29, 76, 0.82);
+ color: #fff;
+}
+
+.cn1-howdoi-card__body {
+ padding: 0.95rem 1rem 1rem;
+}
+
+.cn1-howdoi-card__body h2 {
+ margin: 0;
+ font-size: 1.03rem;
+ line-height: 1.3;
+}
+
+.cn1-howdoi-card__body h2 a {
+ color: var(--cn1-howdoi-title);
+}
+
+.cn1-howdoi-card__body p {
+ margin: 0.5rem 0 0;
+ color: var(--cn1-howdoi-text);
+ font-size: 0.95rem;
+ line-height: 1.45;
+}
+
+.cn1-howdoi-card__meta {
+ margin-top: 0.8rem;
+ display: flex;
+ gap: 0.45rem;
+ flex-wrap: wrap;
+}
+
+.cn1-howdoi-card__meta span {
+ font-size: 0.72rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ padding: 0.25rem 0.5rem;
+ border-radius: 999px;
+ background: var(--cn1-howdoi-chip);
+ color: var(--cn1-howdoi-title);
+}
+
+.cn1-howdoi-single {
+ --cn1-howdoi-panel: rgba(29, 82, 255, 0.08);
+ --cn1-howdoi-panel-border: rgba(29, 82, 255, 0.25);
+}
+
+body.dark .cn1-howdoi-single {
+ --cn1-howdoi-panel: rgba(175, 193, 255, 0.1);
+ --cn1-howdoi-panel-border: rgba(175, 193, 255, 0.25);
+}
+
+.cn1-howdoi-single .post-header {
+ margin-bottom: 1rem;
+}
+
+.cn1-howdoi-single__eyebrow {
+ margin: 0.1rem 0 0.5rem;
+ font-size: 0.76rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ font-weight: 700;
+ color: var(--cn1-howdoi-accent, #1d52ff);
+}
+
+.cn1-howdoi-single__layout {
+ display: grid;
+ grid-template-columns: 300px 1fr;
+ gap: 1.1rem;
+ align-items: start;
+}
+
+.cn1-howdoi-single__aside {
+ position: sticky;
+ top: 6.5rem;
+ border: 1px solid var(--cn1-howdoi-panel-border);
+ background: var(--cn1-howdoi-panel);
+ border-radius: 14px;
+ padding: 0.9rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.cn1-howdoi-single__aside img {
+ width: 100%;
+ border-radius: 10px;
+}
+
+.cn1-howdoi-single__watch,
+.cn1-howdoi-single__back {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.45rem;
+ padding: 0.52rem 0.7rem;
+ border-radius: 9px;
+ border: 1px solid var(--cn1-howdoi-panel-border);
+ font-weight: 600;
+ text-decoration: none;
+}
+
+.cn1-howdoi-single__watch {
+ background: var(--cn1-howdoi-accent, #1d52ff);
+ color: #fff;
+ border-color: transparent;
+}
+
+.cn1-howdoi-single__watch:hover {
+ filter: brightness(1.05);
+}
+
+.cn1-howdoi-single__back {
+ color: inherit;
+}
+
+.cn1-howdoi-single__tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+}
+
+.cn1-howdoi-single__tags span {
+ font-size: 0.74rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ padding: 0.22rem 0.5rem;
+ border-radius: 999px;
+ border: 1px solid var(--cn1-howdoi-panel-border);
+}
+
+.cn1-howdoi-single__content {
+ border: 1px solid var(--cn1-howdoi-panel-border);
+ background: var(--cn1-howdoi-panel);
+ border-radius: 14px;
+ padding: 1.2rem 1.25rem;
+}
+
+.cn1-howdoi-single__content h4 {
+ margin-top: 0.8rem;
+}
+
+@media (max-width: 960px) {
+ .cn1-howdoi-single__layout {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-howdoi-single__aside {
+ position: static;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-introduction.css b/docs/website/assets/css/extended/cn1-introduction.css
new file mode 100644
index 0000000000..79913d4798
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-introduction.css
@@ -0,0 +1,86 @@
+.post-single--introduction .post-content > h2 {
+ margin-top: 1.9rem;
+ margin-bottom: 0.65rem;
+ padding: 0.65rem 0.9rem;
+ border-left: 4px solid #1d52ff;
+ border-radius: 10px;
+ background: rgba(29, 82, 255, 0.08);
+}
+
+body.dark .post-single--introduction .post-content > h2 {
+ border-left-color: #afc1ff;
+ background: rgba(175, 193, 255, 0.12);
+}
+
+.post-single--introduction .post-content > h3 {
+ margin-top: 1.2rem;
+ margin-bottom: 0.45rem;
+}
+
+.post-single--introduction .post-content img[src*="Group-1851.png"] {
+ display: block;
+ width: min(820px, 100%);
+ margin: 1rem auto 1.4rem;
+}
+
+.post-single--introduction .post-content img[src*="Group-2326.svg"] {
+ width: min(420px, 100%);
+ float: right;
+ margin: 0 0 1rem 1.2rem;
+}
+
+.post-single--introduction .post-content details {
+ margin: 0.7rem 0;
+ border: 1px solid rgba(29, 82, 255, 0.28);
+ border-radius: 10px;
+ background: rgba(29, 82, 255, 0.04);
+ overflow: hidden;
+}
+
+body.dark .post-single--introduction .post-content details {
+ border-color: rgba(175, 193, 255, 0.3);
+ background: rgba(175, 193, 255, 0.08);
+}
+
+.post-single--introduction .post-content details summary {
+ list-style: none;
+ cursor: pointer;
+ padding: 0.7rem 0.95rem;
+ font-weight: 600;
+ color: #1d52ff;
+}
+
+body.dark .post-single--introduction .post-content details summary {
+ color: #afc1ff;
+}
+
+.post-single--introduction .post-content details summary::-webkit-details-marker {
+ display: none;
+}
+
+.post-single--introduction .post-content details[open] summary {
+ border-bottom: 1px solid rgba(29, 82, 255, 0.2);
+}
+
+body.dark .post-single--introduction .post-content details[open] summary {
+ border-bottom-color: rgba(175, 193, 255, 0.25);
+}
+
+.post-single--introduction .post-content details > :not(summary) {
+ padding: 0.8rem 0.95rem 0.9rem;
+ margin: 0;
+}
+
+.post-single--introduction .post-content::after {
+ content: "";
+ display: block;
+ clear: both;
+}
+
+@media (max-width: 900px) {
+ .post-single--introduction .post-content img[src*="Group-2326.svg"] {
+ float: none;
+ display: block;
+ margin: 0.4rem auto 1rem;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-libs.css b/docs/website/assets/css/extended/cn1-libs.css
new file mode 100644
index 0000000000..5b38c96dca
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-libs.css
@@ -0,0 +1,101 @@
+.cn1-libs-page__intro {
+ margin-bottom: 1.25rem;
+}
+
+.cn1-libs-page__warning {
+ margin: 1rem 0 0;
+ padding: 0.85rem 1rem;
+ border: 1px solid rgba(245, 164, 72, 0.5);
+ border-radius: 10px;
+ background: rgba(245, 164, 72, 0.12);
+ color: #ffd8a6;
+}
+
+body.light .cn1-libs-page__warning {
+ background: rgba(245, 164, 72, 0.14);
+ color: #5f3f10;
+}
+
+.cn1-libs-grid {
+ margin-top: 1.1rem;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+ gap: 0.9rem;
+}
+
+.cn1-libs-card {
+ border: 1px solid var(--cn1-course-card-border, rgba(29, 82, 255, 0.45));
+ border-radius: 12px;
+ background: var(--cn1-course-card-bg, rgba(68, 82, 123, 0.25));
+ padding: 0.95rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.cn1-libs-card h2 {
+ margin: 0;
+ font-size: 1.02rem;
+ line-height: 1.3;
+ color: var(--cn1-course-link-strong, #f5f8ff);
+}
+
+.cn1-libs-card__meta {
+ margin-top: 0.4rem;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+}
+
+.cn1-libs-card__meta span {
+ display: inline-block;
+ padding: 0.15rem 0.5rem;
+ border-radius: 999px;
+ font-size: 0.72rem;
+ font-weight: 600;
+ color: var(--cn1-course-link-strong, #f5f8ff);
+ border: 1px solid var(--cn1-course-card-border, rgba(29, 82, 255, 0.45));
+ background: rgba(8, 17, 55, 0.22);
+}
+
+body.light .cn1-libs-card__meta span {
+ background: rgba(255, 255, 255, 0.72);
+}
+
+.cn1-libs-card p {
+ margin: 0;
+ color: var(--cn1-course-soft-text, rgba(217, 228, 255, 0.85));
+ font-size: 0.92rem;
+ line-height: 1.45;
+}
+
+.cn1-libs-card__cta {
+ margin-top: auto;
+ display: inline-flex;
+ align-self: flex-start;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 0.86rem;
+ color: var(--cn1-course-link-strong, #f5f8ff);
+ border: 1px solid var(--cn1-course-card-border, rgba(29, 82, 255, 0.45));
+ border-radius: 8px;
+ padding: 0.45rem 0.65rem;
+ background: rgba(8, 17, 55, 0.22);
+}
+
+body.light .cn1-libs-card__cta {
+ background: rgba(255, 255, 255, 0.76);
+}
+
+.cn1-libs-card__cta:hover {
+ border-color: var(--cn1-course-link, #afc1ff);
+ color: var(--cn1-course-link, #afc1ff);
+}
+
+.cn1-libs-empty {
+ margin-top: 1rem;
+ border: 1px dashed var(--cn1-course-card-border, rgba(29, 82, 255, 0.45));
+ border-radius: 12px;
+ padding: 1rem;
+ color: var(--cn1-course-soft-text, rgba(217, 228, 255, 0.85));
+}
diff --git a/docs/website/assets/css/extended/cn1-markdown.css b/docs/website/assets/css/extended/cn1-markdown.css
new file mode 100644
index 0000000000..0970028298
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-markdown.css
@@ -0,0 +1,131 @@
+.main:has(> .post-single) {
+ max-width: min(1180px, calc(100vw - 32px));
+ padding-inline: 24px;
+}
+
+.post-single {
+ font-family: "Poppins", sans-serif;
+}
+
+.post-single .post-title {
+ margin: 0;
+ color: rgb(28, 81, 255);
+ font-family: "Poppins", sans-serif;
+ font-size: 35px;
+ font-weight: 500;
+ line-height: 45.5px;
+}
+
+body.dark .post-single .post-title {
+ color: #f5f8ff;
+}
+
+.post-single .post-content,
+.post-single .post-description,
+.post-single .post-meta,
+.post-single .post-content p,
+.post-single .post-content li,
+.post-single .post-content a,
+.post-single .post-content blockquote {
+ font-family: "Poppins", sans-serif;
+}
+
+.post-single .post-content {
+ color: #273457;
+}
+
+body.dark .post-single .post-content {
+ color: #dbe4ff;
+}
+
+.post-single .post-content h1,
+.post-single .post-content h2,
+.post-single .post-content h3,
+.post-single .post-content h4,
+.post-single .post-content h5,
+.post-single .post-content h6 {
+ font-family: "Poppins", sans-serif;
+ font-weight: 500;
+ color: rgb(28, 81, 255);
+ line-height: 1.3;
+}
+
+body.dark .post-single .post-content h1,
+body.dark .post-single .post-content h2,
+body.dark .post-single .post-content h3,
+body.dark .post-single .post-content h4,
+body.dark .post-single .post-content h5,
+body.dark .post-single .post-content h6 {
+ color: #f5f8ff;
+}
+
+.post-single .post-content .cn1-embed {
+ margin: 16px 0 24px;
+}
+
+.post-single .post-content .cn1-embed iframe {
+ width: 100%;
+ max-width: 100%;
+ min-height: 360px;
+ border: 0;
+}
+
+.post-single .post-content h1 {
+ font-size: 35px;
+}
+
+.post-single .post-content h2 {
+ font-size: 30px;
+}
+
+.post-single .post-content h3 {
+ font-size: 26px;
+}
+
+.post-single .post-content h4 {
+ font-size: 22px;
+}
+
+.post-single .post-content h5 {
+ font-size: 18px;
+}
+
+.post-single .post-content h6 {
+ font-size: 16px;
+}
+
+@media (max-width: 900px) {
+ .main:has(> .post-single) {
+ max-width: calc(100vw - 20px);
+ padding-inline: 14px;
+ }
+
+ .post-single .post-title {
+ font-size: 30px;
+ line-height: 1.3;
+ }
+
+ .post-single .post-content h1 {
+ font-size: 30px;
+ }
+
+ .post-single .post-content h2 {
+ font-size: 27px;
+ }
+
+ .post-single .post-content h3 {
+ font-size: 24px;
+ }
+
+ .post-single .post-content h4 {
+ font-size: 21px;
+ }
+
+ .post-single .post-content h5 {
+ font-size: 18px;
+ }
+
+ .post-single .post-content h6 {
+ font-size: 16px;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-press.css b/docs/website/assets/css/extended/cn1-press.css
new file mode 100644
index 0000000000..31a4027fa6
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-press.css
@@ -0,0 +1,320 @@
+.post-single--press {
+ font-family: "Poppins", sans-serif;
+}
+
+.post-single--press .post-header {
+ margin-bottom: 16px;
+}
+
+.post-single--press .post-title {
+ margin-bottom: 8px;
+}
+
+.post-single--press .post-description {
+ margin: 0;
+ color: #4a5678;
+ font-size: 16px;
+}
+
+body.dark .post-single--press .post-description {
+ color: #afc1ff;
+}
+
+.cn1-press-tabs {
+ display: inline-flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 18px;
+ border-bottom: 1px solid rgba(28, 81, 255, 0.24);
+ padding-bottom: 8px;
+}
+
+.cn1-press-tabs__btn {
+ border: 0;
+ border-radius: 8px;
+ padding: 8px 14px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ background: rgba(28, 81, 255, 0.1);
+ color: #1c51ff;
+}
+
+.cn1-press-tabs__btn.is-active {
+ background: #1c51ff;
+ color: #f5f8ff;
+}
+
+body.dark .cn1-press-tabs {
+ border-bottom-color: rgba(175, 193, 255, 0.3);
+}
+
+body.dark .cn1-press-tabs__btn {
+ background: rgba(175, 193, 255, 0.14);
+ color: #afc1ff;
+}
+
+body.dark .cn1-press-tabs__btn.is-active {
+ background: #1d52ff;
+ color: #f5f8ff;
+}
+
+.cn1-press-panel {
+ padding-top: 6px;
+}
+
+.cn1-press-list {
+ display: grid;
+ gap: 16px;
+}
+
+.cn1-press-item {
+ display: grid;
+ grid-template-columns: 190px 1fr;
+ gap: 18px;
+ align-items: start;
+ padding: 14px 0 18px;
+ border-bottom: 1px solid rgba(28, 81, 255, 0.16);
+}
+
+body.dark .cn1-press-item {
+ border-bottom-color: rgba(175, 193, 255, 0.15);
+}
+
+.cn1-press-item__logo-wrap {
+ min-height: 82px;
+ display: flex;
+ align-items: center;
+}
+
+.cn1-press-item__logo {
+ max-width: 180px;
+ max-height: 76px;
+ width: auto;
+ height: auto;
+ object-fit: contain;
+}
+
+.cn1-press-item__quote,
+.cn1-press-item__headline {
+ margin: 0 0 8px;
+ color: #25345a;
+ line-height: 1.5;
+}
+
+.cn1-press-item__headline {
+ font-weight: 600;
+}
+
+.cn1-press-item__source {
+ margin: 0 0 10px;
+ color: #1c51ff;
+ font-weight: 600;
+ font-size: 14px;
+}
+
+body.dark .cn1-press-item__quote,
+body.dark .cn1-press-item__headline {
+ color: #e3ebff;
+}
+
+body.dark .cn1-press-item__source {
+ color: #afc1ff;
+}
+
+.cn1-press-item__links {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.cn1-press-item__links a {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ color: #1c51ff;
+ font-size: 13px;
+ font-weight: 600;
+ text-decoration: none;
+}
+
+.cn1-press-item__links i {
+ width: 20px;
+ height: 20px;
+ border-radius: 999px;
+ border: 1px solid rgba(28, 81, 255, 0.55);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 10px;
+}
+
+body.dark .cn1-press-item__links a {
+ color: #afc1ff;
+}
+
+body.dark .cn1-press-item__links i {
+ border-color: rgba(175, 193, 255, 0.55);
+}
+
+.cn1-release-list {
+ display: grid;
+ gap: 16px;
+}
+
+.cn1-release-item {
+ display: grid;
+ grid-template-columns: 64px 1fr;
+ gap: 16px;
+ align-items: center;
+ padding: 14px 0;
+ border-bottom: 1px solid rgba(28, 81, 255, 0.16);
+}
+
+body.dark .cn1-release-item {
+ border-bottom-color: rgba(175, 193, 255, 0.15);
+}
+
+.cn1-release-item__icon img {
+ width: 52px;
+ height: 52px;
+}
+
+.cn1-release-item__body h3 {
+ margin: 0 0 6px;
+ font-size: 19px;
+}
+
+.cn1-release-item__body h3 a {
+ color: #1f2e59;
+ text-decoration: none;
+}
+
+.cn1-release-item__body h3 a:hover {
+ color: #1c51ff;
+}
+
+.cn1-release-item__body p {
+ margin: 0;
+ color: #536082;
+ font-size: 14px;
+}
+
+body.dark .cn1-release-item__body h3 a {
+ color: #f5f8ff;
+}
+
+body.dark .cn1-release-item__body h3 a:hover {
+ color: #afc1ff;
+}
+
+body.dark .cn1-release-item__body p {
+ color: #afc1ff;
+}
+
+.cn1-press-kit h2 {
+ margin: 0 0 8px;
+ font-size: 28px;
+}
+
+.cn1-press-kit > p {
+ margin: 0 0 16px;
+ color: #556285;
+}
+
+body.dark .cn1-press-kit > p {
+ color: #afc1ff;
+}
+
+.cn1-press-kit__grid {
+ display: grid;
+ grid-template-columns: 240px 1fr;
+ gap: 0;
+ border: 1px solid rgba(28, 81, 255, 0.22);
+ border-radius: 10px;
+ overflow: hidden;
+}
+
+body.dark .cn1-press-kit__grid {
+ border-color: rgba(175, 193, 255, 0.28);
+}
+
+.cn1-press-kit__label,
+.cn1-press-kit__value {
+ padding: 14px;
+ border-bottom: 1px solid rgba(28, 81, 255, 0.12);
+}
+
+.cn1-press-kit__label {
+ font-size: 14px;
+ font-weight: 700;
+ color: #1c51ff;
+ background: rgba(28, 81, 255, 0.06);
+}
+
+.cn1-press-kit__value {
+ color: #2f3a61;
+}
+
+.cn1-press-kit__value p {
+ margin: 0 0 6px;
+}
+
+.cn1-press-kit__value p:last-child {
+ margin-bottom: 0;
+}
+
+body.dark .cn1-press-kit__label,
+body.dark .cn1-press-kit__value {
+ border-bottom-color: rgba(175, 193, 255, 0.16);
+}
+
+body.dark .cn1-press-kit__label {
+ color: #afc1ff;
+ background: rgba(175, 193, 255, 0.09);
+}
+
+body.dark .cn1-press-kit__value {
+ color: #f5f8ff;
+}
+
+.cn1-press-kit__download {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ color: #1c51ff;
+ font-weight: 600;
+ text-decoration: none;
+}
+
+body.dark .cn1-press-kit__download {
+ color: #afc1ff;
+}
+
+@media (max-width: 920px) {
+ .cn1-press-item {
+ grid-template-columns: 1fr;
+ gap: 10px;
+ }
+
+ .cn1-press-item__logo-wrap {
+ min-height: 0;
+ }
+
+ .cn1-press-kit__grid {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-press-kit__label,
+ .cn1-press-kit__value {
+ border-bottom: 0;
+ }
+
+ .cn1-press-kit__label {
+ border-top: 1px solid rgba(28, 81, 255, 0.16);
+ }
+
+ .cn1-press-kit__grid .cn1-press-kit__label:first-child {
+ border-top: 0;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-pricing.css b/docs/website/assets/css/extended/cn1-pricing.css
new file mode 100644
index 0000000000..cd470ea977
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-pricing.css
@@ -0,0 +1,325 @@
+.cn1-pricing-page {
+ max-width: 1200px;
+ color: #f5f8ff;
+}
+
+body:not(.dark) .cn1-pricing-page {
+ color: #1a2c57;
+}
+
+.cn1-pricing-header {
+ text-align: center;
+}
+
+.cn1-pricing-subtitle {
+ margin: 0.35rem 0 0;
+ color: #afc1ff;
+ font-size: 1.05rem;
+ font-weight: 500;
+}
+
+.cn1-pricing-note {
+ margin: 0.45rem 0 0;
+ color: rgba(245, 248, 255, 0.78);
+ font-size: 0.92rem;
+}
+
+body:not(.dark) .cn1-pricing-subtitle {
+ color: #1d52ff;
+}
+
+body:not(.dark) .cn1-pricing-note {
+ color: #536184;
+}
+
+.cn1-pricing-toggle {
+ margin: 1.35rem auto 1rem;
+ padding: 0.9rem 1rem;
+ display: grid;
+ gap: 0.6rem;
+ justify-items: center;
+}
+
+body:not(.dark) .cn1-pricing-toggle {
+ background: transparent;
+}
+
+.cn1-pricing-toggle__label {
+ font-size: 0.8rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: #afc1ff;
+ font-weight: 600;
+}
+
+body:not(.dark) .cn1-pricing-toggle__label {
+ color: #1d52ff;
+}
+
+.cn1-pricing-toggle__switch {
+ display: inline-flex;
+ border: 1px solid rgba(175, 193, 255, 0.35);
+ border-radius: 2px;
+ padding: 0.2rem;
+ background: rgba(8, 17, 55, 0.25);
+}
+
+body:not(.dark) .cn1-pricing-toggle__switch {
+ background: #fff;
+ border-color: #d0dcff;
+}
+
+.cn1-pricing-toggle__switch button {
+ border: 0;
+ background: transparent;
+ color: rgba(245, 248, 255, 0.78);
+ font-family: "Poppins", sans-serif;
+ font-size: 0.86rem;
+ font-weight: 600;
+ padding: 0.4rem 0.9rem;
+ border-radius: 2px;
+ cursor: pointer;
+}
+
+body:not(.dark) .cn1-pricing-toggle__switch button {
+ color: #4d5b85;
+}
+
+.cn1-pricing-toggle__switch button.is-active {
+ background: #1d52ff;
+ color: #f5f8ff;
+}
+
+.cn1-pricing-toggle__hint {
+ margin: 0;
+ font-size: 0.82rem;
+ color: rgba(245, 248, 255, 0.72);
+ text-align: center;
+}
+
+body:not(.dark) .cn1-pricing-toggle__hint {
+ color: #627199;
+}
+
+.cn1-pricing-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 0.9rem;
+ align-items: stretch;
+}
+
+.cn1-price-card {
+ border: 1px solid rgba(29, 82, 255, 0.45);
+ border-radius: 2px;
+ background: rgba(62, 74, 112, 0.38);
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+}
+
+body:not(.dark) .cn1-price-card {
+ background: #f5f8ff;
+ border-color: #d8e2ff;
+}
+
+.cn1-price-card--featured {
+ border-color: #1d52ff;
+ box-shadow: 0 12px 30px rgba(5, 15, 54, 0.25);
+}
+
+body:not(.dark) .cn1-price-card--featured {
+ box-shadow: 0 10px 24px rgba(19, 70, 209, 0.12);
+}
+
+.cn1-price-card header {
+ margin-bottom: 0.8rem;
+}
+
+.cn1-price-card__badge {
+ margin: 0 0 0.35rem;
+ display: inline-block;
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ background: #1d52ff;
+ color: #f5f8ff;
+ border-radius: 2px;
+ padding: 0.2rem 0.45rem;
+}
+
+.cn1-price-card__tier {
+ margin: 0;
+ font-size: 1.2rem;
+ font-weight: 700;
+ color: #f5f8ff;
+}
+
+body:not(.dark) .cn1-price-card__tier {
+ color: #102154;
+}
+
+.cn1-price-card__price {
+ margin: 0.35rem 0 0;
+ font-size: 0.9rem;
+ color: rgba(245, 248, 255, 0.9);
+}
+
+.cn1-price-card__price span {
+ font-size: 1.65rem;
+ font-weight: 800;
+ color: #f5f8ff;
+}
+
+body:not(.dark) .cn1-price-card__price,
+body:not(.dark) .cn1-price-card__price span {
+ color: #132b63;
+}
+
+.cn1-price-card__bill {
+ margin: 0.2rem 0 0;
+ color: #afc1ff;
+ font-size: 0.78rem;
+ min-height: 2.1em;
+}
+
+body:not(.dark) .cn1-price-card__bill {
+ color: #2e55c4;
+}
+
+.cn1-price-card ul {
+ list-style: none;
+ padding: 0;
+ margin: 0.2rem 0 0.95rem;
+ display: grid;
+ gap: 0.45rem;
+}
+
+.cn1-price-card li {
+ position: relative;
+ padding-left: 1rem;
+ color: rgba(245, 248, 255, 0.9);
+ font-size: 0.86rem;
+ line-height: 1.35;
+}
+
+.cn1-price-card li::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 0.5em;
+ width: 0.35rem;
+ height: 0.35rem;
+ border-radius: 50%;
+ background: #1d52ff;
+}
+
+body:not(.dark) .cn1-price-card li {
+ color: #2d3c66;
+}
+
+.cn1-price-card__cta {
+ margin-top: auto;
+ text-align: center;
+ text-decoration: none;
+ border: 1px solid rgba(245, 248, 255, 0.75);
+ color: #f5f8ff;
+ border-radius: 2px;
+ padding: 0.55rem 0.7rem;
+ font-weight: 600;
+ font-size: 0.9rem;
+ appearance: none;
+ background: transparent;
+ cursor: pointer;
+ width: 100%;
+ font-family: "Poppins", sans-serif;
+ line-height: 1.2;
+}
+
+.cn1-price-card__cta:hover {
+ background: rgba(29, 82, 255, 0.2);
+ border-color: #1d52ff;
+}
+
+body:not(.dark) .cn1-price-card__cta {
+ color: #16316c;
+ border-color: #bfd0ff;
+}
+
+body:not(.dark) .cn1-price-card__cta:hover {
+ background: #edf2ff;
+}
+
+.cn1-pricing-faq {
+ margin-top: 2rem;
+ padding: 0.2rem 0 0;
+}
+
+body:not(.dark) .cn1-pricing-faq {
+ background: transparent;
+}
+
+.cn1-pricing-faq h2,
+.cn1-pricing-faq h3 {
+ margin-top: 1rem;
+}
+
+body:not(.dark) .cn1-pricing-faq h2,
+body:not(.dark) .cn1-pricing-faq h3,
+body:not(.dark) .cn1-pricing-faq p,
+body:not(.dark) .cn1-pricing-faq li,
+body:not(.dark) .cn1-pricing-faq strong {
+ color: #203362;
+}
+
+body:not(.dark) .cn1-pricing-faq a {
+ color: #1d52ff;
+}
+
+.cn1-pricing-page .cn1-loved {
+ margin-top: 2rem;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+body:not(.dark) .cn1-pricing-page .cn1-loved h2 {
+ color: #1d52ff;
+}
+
+body:not(.dark) .cn1-pricing-page .cn1-loved__grid > img {
+ opacity: 1;
+ filter: none;
+}
+
+body:not(.dark) .cn1-pricing-page .cn1-loved__grid .cn1-carousel__slide blockquote {
+ color: #1b274e;
+ background: #e7ecfb;
+ border-color: #2b66ea;
+}
+
+body:not(.dark) .cn1-pricing-page .cn1-loved__grid .cn1-carousel__slide blockquote span + span {
+ color: #4a5f97;
+}
+
+body:not(.dark) .cn1-pricing-page .cn1-loved__grid .cn1-carousel__slide blockquote::after {
+ background: #e7ecfb;
+ border-right-color: #2b66ea;
+ border-bottom-color: #2b66ea;
+}
+
+body:not(.dark) .cn1-pricing-page .cn1-loved__grid .cn1-carousel__slide h3,
+body:not(.dark) .cn1-pricing-page .cn1-loved__grid .cn1-carousel__slide p {
+ color: #1b274e;
+}
+
+@media (max-width: 1120px) {
+ .cn1-pricing-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 700px) {
+ .cn1-pricing-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-search.css b/docs/website/assets/css/extended/cn1-search.css
new file mode 100644
index 0000000000..b04d77df19
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-search.css
@@ -0,0 +1,96 @@
+.cn1-menu-search-link {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.05rem;
+ line-height: 1;
+ min-width: 1.25rem;
+}
+
+.cn1-search-shell {
+ border: 1px solid rgba(29, 82, 255, 0.35);
+ border-radius: 14px;
+ padding: 1rem;
+ background: rgba(68, 82, 123, 0.22);
+}
+
+body.light .cn1-search-shell {
+ background: #f5f8ff;
+ border-color: #d7e2ff;
+}
+
+.cn1-search-label {
+ display: block;
+ font-size: 0.86rem;
+ font-weight: 600;
+ margin-bottom: 0.35rem;
+ color: var(--cn1-course-link, #afc1ff);
+}
+
+.cn1-search-input {
+ width: 100%;
+ border: 1px solid rgba(175, 193, 255, 0.4);
+ border-radius: 10px;
+ padding: 0.75rem 0.85rem;
+ background: rgba(8, 17, 55, 0.5);
+ color: #f5f8ff;
+ font-family: "Poppins", sans-serif;
+ font-size: 0.98rem;
+}
+
+body.light .cn1-search-input {
+ background: #fff;
+ color: #1c2a55;
+ border-color: #c9d6ff;
+}
+
+.cn1-search-status {
+ margin: 0.7rem 0 0;
+ font-size: 0.86rem;
+ color: var(--cn1-course-soft-text, rgba(217, 228, 255, 0.85));
+}
+
+.cn1-search-results {
+ margin-top: 0.9rem;
+ display: grid;
+ gap: 0.75rem;
+}
+
+.cn1-search-result {
+ border: 1px solid rgba(175, 193, 255, 0.25);
+ border-radius: 10px;
+ padding: 0.75rem 0.8rem;
+ background: rgba(8, 17, 55, 0.22);
+}
+
+body.light .cn1-search-result {
+ background: rgba(255, 255, 255, 0.75);
+ border-color: #d7e2ff;
+}
+
+.cn1-search-result h2 {
+ margin: 0;
+ font-size: 1rem;
+}
+
+.cn1-search-result h2 a {
+ color: var(--cn1-course-link-strong, #f5f8ff);
+ text-decoration: none;
+}
+
+.cn1-search-result h2 a:hover {
+ color: var(--cn1-course-link, #afc1ff);
+}
+
+.cn1-search-result p {
+ margin: 0.4rem 0;
+ font-size: 0.9rem;
+ line-height: 1.45;
+ color: var(--cn1-course-soft-text, rgba(217, 228, 255, 0.85));
+}
+
+.cn1-search-result__url {
+ font-size: 0.8rem;
+ text-decoration: none;
+ color: var(--cn1-course-link, #afc1ff);
+}
diff --git a/docs/website/assets/css/extended/cn1-team.css b/docs/website/assets/css/extended/cn1-team.css
new file mode 100644
index 0000000000..296a7ce713
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-team.css
@@ -0,0 +1,60 @@
+.post-single--team .post-content > h2 {
+ margin-top: 0.3rem;
+ margin-bottom: 1rem;
+}
+
+.post-single--team .post-content > h3 {
+ margin-top: 1.4rem;
+ margin-bottom: 0.55rem;
+ padding: 0.6rem 0.8rem;
+ border-left: 4px solid #1d52ff;
+ border-radius: 10px;
+ background: rgba(29, 82, 255, 0.08);
+}
+
+body.dark .post-single--team .post-content > h3 {
+ border-left-color: #afc1ff;
+ background: rgba(175, 193, 255, 0.12);
+}
+
+.post-single--team .post-content > p > img {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 2px solid rgba(29, 82, 255, 0.4);
+ float: right;
+ margin: 0.1rem 0 0.7rem 1rem;
+}
+
+body.dark .post-single--team .post-content > p > img {
+ border-color: rgba(175, 193, 255, 0.5);
+}
+
+.post-single--team .post-content > ul {
+ margin-top: 0.55rem;
+ margin-bottom: 1.2rem;
+}
+
+.post-single--team .post-content > hr {
+ margin: 1.2rem 0 1rem;
+ border-color: rgba(29, 82, 255, 0.22);
+}
+
+body.dark .post-single--team .post-content > hr {
+ border-color: rgba(175, 193, 255, 0.25);
+}
+
+.post-single--team .post-content::after {
+ content: "";
+ display: block;
+ clear: both;
+}
+
+@media (max-width: 760px) {
+ .post-single--team .post-content > p > img {
+ float: none;
+ display: block;
+ margin: 0.25rem auto 0.8rem;
+ }
+}
diff --git a/docs/website/assets/css/extended/cn1-videos.css b/docs/website/assets/css/extended/cn1-videos.css
new file mode 100644
index 0000000000..9060f57220
--- /dev/null
+++ b/docs/website/assets/css/extended/cn1-videos.css
@@ -0,0 +1,133 @@
+.post-single--videos {
+ font-family: "Poppins", sans-serif;
+}
+
+.post-single--videos .post-description {
+ margin: 0;
+ color: #556285;
+}
+
+body.dark .post-single--videos .post-description {
+ color: #afc1ff;
+}
+
+.cn1-videos-hero {
+ margin: 22px 0 20px;
+ padding: 18px 20px;
+ border: 1px solid rgba(28, 81, 255, 0.2);
+ border-radius: 12px;
+ background: linear-gradient(145deg, rgba(28, 81, 255, 0.08), rgba(28, 81, 255, 0.03));
+}
+
+body.dark .cn1-videos-hero {
+ border-color: rgba(175, 193, 255, 0.25);
+ background: linear-gradient(145deg, rgba(175, 193, 255, 0.13), rgba(175, 193, 255, 0.06));
+}
+
+.cn1-videos-hero__eyebrow {
+ margin: 0 0 6px;
+ color: #1c51ff;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+body.dark .cn1-videos-hero__eyebrow {
+ color: #afc1ff;
+}
+
+.cn1-videos-hero h2 {
+ margin: 0;
+ font-size: 28px;
+ line-height: 1.22;
+}
+
+.cn1-videos-hero p {
+ margin: 10px 0 0;
+ color: #334371;
+ line-height: 1.55;
+}
+
+body.dark .cn1-videos-hero p {
+ color: #dce5ff;
+}
+
+.cn1-videos-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ gap: 18px;
+}
+
+.cn1-video-card {
+ border: 1px solid rgba(28, 81, 255, 0.18);
+ border-radius: 12px;
+ overflow: hidden;
+ background: rgba(255, 255, 255, 0.76);
+}
+
+body.dark .cn1-video-card {
+ border-color: rgba(175, 193, 255, 0.2);
+ background: rgba(10, 24, 70, 0.85);
+}
+
+.cn1-video-card__embed {
+ position: relative;
+ aspect-ratio: 16 / 9;
+ width: 100%;
+ background: #121722;
+}
+
+.cn1-video-card__embed iframe {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ display: block;
+}
+
+.cn1-video-card__meta {
+ padding: 14px 14px 16px;
+}
+
+.cn1-video-card__index {
+ margin: 0 0 6px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: #1c51ff;
+}
+
+body.dark .cn1-video-card__index {
+ color: #afc1ff;
+}
+
+.cn1-video-card__meta h3 {
+ margin: 0 0 12px;
+ font-size: 19px;
+ line-height: 1.35;
+}
+
+.cn1-video-card__meta a {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ color: #1c51ff;
+ text-decoration: none;
+}
+
+body.dark .cn1-video-card__meta a {
+ color: #afc1ff;
+}
+
+@media (max-width: 760px) {
+ .cn1-videos-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .cn1-videos-hero h2 {
+ font-size: 23px;
+ }
+}
diff --git a/docs/website/assets/js/cn1-crisp.js b/docs/website/assets/js/cn1-crisp.js
new file mode 100644
index 0000000000..9e71d9ebf1
--- /dev/null
+++ b/docs/website/assets/js/cn1-crisp.js
@@ -0,0 +1,135 @@
+(() => {
+ const CONSENT_KEY = "cn1-crisp-consent-v1";
+ const CONSENT_COOKIE = "cn1_crisp_consent";
+ const WEBSITE_ID = "e0201fca-1e59-4f30-9d00-8c37aa18293e";
+ const CONSENT_TTL_DAYS = 365;
+
+ const readCookie = (name) => {
+ const prefix = `${name}=`;
+ const found = document.cookie
+ .split(";")
+ .map((part) => part.trim())
+ .find((part) => part.startsWith(prefix));
+ return found ? decodeURIComponent(found.substring(prefix.length)) : null;
+ };
+
+ const writeCookie = (name, value, days) => {
+ const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
+ document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
+ };
+
+ const getConsent = () => {
+ const cookieValue = readCookie(CONSENT_COOKIE);
+ if (cookieValue === "accepted" || cookieValue === "declined") {
+ return cookieValue;
+ }
+ try {
+ const storageValue = localStorage.getItem(CONSENT_KEY);
+ if (storageValue === "accepted" || storageValue === "declined") {
+ return storageValue;
+ }
+ } catch (e) {
+ // no-op
+ }
+ return null;
+ };
+
+ const setConsent = (value) => {
+ writeCookie(CONSENT_COOKIE, value, CONSENT_TTL_DAYS);
+ try {
+ localStorage.setItem(CONSENT_KEY, value);
+ } catch (e) {
+ // no-op
+ }
+ };
+
+ const loadCrisp = () => {
+ if (window.CRISP_WEBSITE_ID || document.getElementById("cn1-crisp-loader")) {
+ return;
+ }
+ window.$crisp = window.$crisp || [];
+ window.CRISP_WEBSITE_ID = WEBSITE_ID;
+ const d = document;
+ const s = d.createElement("script");
+ s.id = "cn1-crisp-loader";
+ s.src = "https://client.crisp.chat/l.js";
+ s.async = true;
+ d.head.appendChild(s);
+ };
+
+ const hideCrisp = () => {
+ window.$crisp = window.$crisp || [];
+ try {
+ window.$crisp.push(["do", "chat:hide"]);
+ } catch (e) {
+ // no-op
+ }
+ const crispNode = document.querySelector(".crisp-client");
+ if (crispNode) {
+ crispNode.style.display = "none";
+ }
+ };
+
+ const banner = document.querySelector("[data-cn1-cookie-banner]");
+ const acceptBtn = document.querySelector("[data-cn1-cookie-accept]");
+ const declineBtn = document.querySelector("[data-cn1-cookie-decline]");
+
+ const closeBanner = () => {
+ if (banner) {
+ banner.setAttribute("hidden", "hidden");
+ banner.style.display = "none";
+ }
+ };
+
+ const openBanner = () => {
+ if (banner) {
+ banner.removeAttribute("hidden");
+ banner.style.display = "flex";
+ }
+ };
+
+ const acceptConsent = () => {
+ setConsent("accepted");
+ closeBanner();
+ loadCrisp();
+ };
+
+ const declineConsent = () => {
+ setConsent("declined");
+ closeBanner();
+ hideCrisp();
+ };
+
+ const consent = getConsent();
+ if (consent === "accepted") {
+ loadCrisp();
+ closeBanner();
+ } else if (consent === "declined") {
+ hideCrisp();
+ closeBanner();
+ } else {
+ openBanner();
+ }
+
+ if (acceptBtn) {
+ acceptBtn.addEventListener("click", acceptConsent);
+ }
+
+ if (declineBtn) {
+ declineBtn.addEventListener("click", declineConsent);
+ }
+
+ document.querySelectorAll("[data-cn1-enable-chat]").forEach((link) => {
+ link.addEventListener("click", (event) => {
+ event.preventDefault();
+ acceptConsent();
+ });
+ });
+
+ document.querySelectorAll("[data-cn1-manage-chat]").forEach((link) => {
+ link.addEventListener("click", (event) => {
+ event.preventDefault();
+ openBanner();
+ });
+ });
+})();
diff --git a/docs/website/assets/js/cn1-demo.js b/docs/website/assets/js/cn1-demo.js
new file mode 100644
index 0000000000..3401830c7a
--- /dev/null
+++ b/docs/website/assets/js/cn1-demo.js
@@ -0,0 +1,42 @@
+(() => {
+ const carousels = document.querySelectorAll("[data-cn1-demo-carousel]");
+ carousels.forEach((root) => {
+ const track = root.querySelector("[data-cn1-demo-track]");
+ const slides = Array.from(root.querySelectorAll("[data-cn1-demo-slide]"));
+ const dots = Array.from(root.querySelectorAll("[data-cn1-demo-dot]"));
+ const prev = root.querySelector("[data-cn1-demo-prev]");
+ const next = root.querySelector("[data-cn1-demo-next]");
+ if (!track || slides.length <= 1) return;
+
+ let index = 0;
+
+ const render = () => {
+ track.style.transform = `translateX(-${index * 100}%)`;
+ dots.forEach((dot, i) => {
+ dot.setAttribute("aria-current", i === index ? "true" : "false");
+ });
+ };
+
+ const go = (value) => {
+ const max = slides.length - 1;
+ index = Math.max(0, Math.min(max, value));
+ render();
+ };
+
+ const step = (delta) => {
+ const max = slides.length - 1;
+ let nextIndex = index + delta;
+ if (nextIndex < 0) nextIndex = max;
+ if (nextIndex > max) nextIndex = 0;
+ go(nextIndex);
+ };
+
+ prev?.addEventListener("click", () => step(-1));
+ next?.addEventListener("click", () => step(1));
+ dots.forEach((dot, i) => {
+ dot.addEventListener("click", () => go(i));
+ });
+
+ render();
+ });
+})();
diff --git a/docs/website/assets/js/cn1-home.js b/docs/website/assets/js/cn1-home.js
new file mode 100644
index 0000000000..db83089b3e
--- /dev/null
+++ b/docs/website/assets/js/cn1-home.js
@@ -0,0 +1,73 @@
+(() => {
+ const isHome = !!document.querySelector(".cn1-hero");
+ if (isHome) {
+ document.body.classList.add("cn1-homepage");
+
+ const accordions = document.querySelectorAll(".cn1-accordion");
+ accordions.forEach((accordion) => {
+ const items = Array.from(accordion.querySelectorAll(".cn1-acc-item"));
+ const setState = (item, open) => {
+ const trigger = item.querySelector(".cn1-acc-trigger");
+ const panel = item.querySelector(".cn1-acc-panel");
+ if (!trigger || !panel) return;
+ item.classList.toggle("is-open", open);
+ trigger.setAttribute("aria-expanded", open ? "true" : "false");
+ panel.style.maxHeight = open ? panel.scrollHeight + "px" : "0px";
+ const icon = trigger.querySelector(".cn1-acc-caret i");
+ if (icon) {
+ icon.classList.toggle("fa-chevron-down", open);
+ icon.classList.toggle("fa-chevron-right", !open);
+ }
+ };
+
+ items.forEach((item) => setState(item, item.classList.contains("is-open")));
+ accordion.addEventListener("click", (event) => {
+ const trigger = event.target.closest(".cn1-acc-trigger");
+ if (!trigger) return;
+ const item = trigger.closest(".cn1-acc-item");
+ if (!item) return;
+ const willOpen = !item.classList.contains("is-open");
+ items.forEach((it) => setState(it, false));
+ setState(item, willOpen);
+ });
+ });
+ }
+
+ const carousels = document.querySelectorAll(".cn1-carousel[data-carousel]");
+ carousels.forEach((carousel) => {
+ const track = carousel.querySelector(".cn1-carousel__track");
+ const slides = Array.from(carousel.querySelectorAll(".cn1-carousel__slide"));
+ const dotsWrap = carousel.querySelector(".cn1-carousel__dots");
+ const prev = carousel.querySelector(".cn1-carousel__prev");
+ const next = carousel.querySelector(".cn1-carousel__next");
+ if (!track || slides.length === 0) return;
+
+ let current = slides.findIndex((s) => s.classList.contains("is-active"));
+ if (current < 0) current = 0;
+
+ const setSlide = (index) => {
+ current = (index + slides.length) % slides.length;
+ slides.forEach((slide, i) => slide.classList.toggle("is-active", i === current));
+ track.style.transform = `translateX(${-100 * current}%)`;
+ if (dotsWrap) {
+ dotsWrap.querySelectorAll("button").forEach((dot, i) => {
+ dot.classList.toggle("is-active", i === current);
+ dot.setAttribute("aria-current", i === current ? "true" : "false");
+ });
+ }
+ };
+
+ if (dotsWrap) {
+ dotsWrap.innerHTML = slides
+ .map((_, i) => ``)
+ .join("");
+ dotsWrap.querySelectorAll("button").forEach((dot, i) => {
+ dot.addEventListener("click", () => setSlide(i));
+ });
+ }
+
+ if (prev) prev.addEventListener("click", () => setSlide(current - 1));
+ if (next) next.addEventListener("click", () => setSlide(current + 1));
+ setSlide(current);
+ });
+})();
diff --git a/docs/website/assets/js/cn1-pricing.js b/docs/website/assets/js/cn1-pricing.js
new file mode 100644
index 0000000000..32c7c00bbf
--- /dev/null
+++ b/docs/website/assets/js/cn1-pricing.js
@@ -0,0 +1,59 @@
+(() => {
+ const root = document.querySelector("[data-billing-toggle]");
+ const signupLinks = Array.from(document.querySelectorAll("[data-signup-link][data-level]"));
+ if (!root && signupLinks.length === 0) return;
+
+ const buttons = root ? Array.from(root.querySelectorAll("[data-billing]")) : [];
+ const hint = root ? root.querySelector("[data-billing-hint]") : null;
+ const cards = Array.from(document.querySelectorAll(".cn1-price-card[data-plan]"));
+ const buildSignupUrl = (level, mode) =>
+ `https://cloud.codenameone.com/secure/signup?level=${encodeURIComponent(level)}&mode=${encodeURIComponent(mode)}`;
+
+ const update = (mode) => {
+ if (root) {
+ root.dataset.billingMode = mode;
+ }
+
+ buttons.forEach((btn) => {
+ const active = btn.dataset.billing === mode;
+ btn.classList.toggle("is-active", active);
+ btn.setAttribute("aria-selected", active ? "true" : "false");
+ });
+
+ cards.forEach((card) => {
+ const priceNode = card.querySelector("[data-price]");
+ const billNode = card.querySelector("[data-bill]");
+ const monthly = card.dataset.monthly;
+ const annualMonthly = card.dataset.annualMonthly;
+ const annualTotal = card.dataset.annualTotal;
+
+ if (!priceNode || !billNode) return;
+
+ if (mode === "annual") {
+ priceNode.textContent = annualMonthly;
+ billNode.textContent = `Billed annually at $${annualTotal}/year`;
+ } else {
+ priceNode.textContent = monthly;
+ billNode.textContent = "Billed monthly";
+ }
+ });
+
+ signupLinks.forEach((link) => {
+ const level = (link.dataset.level || "").toLowerCase();
+ if (!level) return;
+ link.href = buildSignupUrl(level, mode);
+ });
+
+ if (hint) {
+ hint.textContent = mode === "annual"
+ ? "Annual billing selected. Prices shown are monthly equivalents with yearly billing."
+ : "Monthly billing selected. Switch to annual to lower the monthly equivalent.";
+ }
+ };
+
+ buttons.forEach((btn) => {
+ btn.addEventListener("click", () => update(btn.dataset.billing));
+ });
+
+ update("monthly");
+})();
diff --git a/docs/website/assets/js/cn1-site.js b/docs/website/assets/js/cn1-site.js
new file mode 100644
index 0000000000..fbad3fb681
--- /dev/null
+++ b/docs/website/assets/js/cn1-site.js
@@ -0,0 +1,140 @@
+(() => {
+ const path = window.location.pathname || "";
+ const isBlogPath = path === "/blog/" || path.startsWith("/blog/");
+ if (isBlogPath) {
+ document.body.classList.add("cn1-blog-post");
+ }
+
+ const mobileMq = window.matchMedia("(max-width: 980px)");
+ const header = document.querySelector(".cn1-header");
+ if (header) {
+ const items = Array.from(header.querySelectorAll("#menu > li.has-children"));
+ const navToggle = header.querySelector("#cn1-nav-toggle");
+
+ const closeAll = () => {
+ items.forEach((item) => {
+ item.classList.remove("open");
+ const trigger = item.querySelector(".cn1-menu-trigger");
+ if (trigger) trigger.setAttribute("aria-expanded", "false");
+ });
+ };
+
+ items.forEach((item) => {
+ const trigger = item.querySelector(".cn1-menu-trigger");
+ if (!trigger) return;
+ trigger.addEventListener("click", (event) => {
+ if (!mobileMq.matches) return;
+ event.preventDefault();
+ const willOpen = !item.classList.contains("open");
+ closeAll();
+ if (willOpen) {
+ item.classList.add("open");
+ trigger.setAttribute("aria-expanded", "true");
+ }
+ });
+ });
+
+ if (navToggle) {
+ navToggle.addEventListener("change", () => {
+ if (!navToggle.checked) closeAll();
+ });
+ }
+
+ const resetDesktop = () => {
+ if (!mobileMq.matches) closeAll();
+ };
+
+ if (mobileMq.addEventListener) {
+ mobileMq.addEventListener("change", resetDesktop);
+ } else if (mobileMq.addListener) {
+ mobileMq.addListener(resetDesktop);
+ }
+
+ const onScroll = () => {
+ header.classList.toggle("cn1-header--compact", window.scrollY > 36);
+ };
+ onScroll();
+ window.addEventListener("scroll", onScroll, { passive: true });
+ }
+
+ const updateThemeUi = () => {
+ const dark = document.body.classList.contains("dark");
+ const label = dark ? "Switch to Light Mode" : "Switch to Dark Mode";
+ document.querySelectorAll("[data-cn1-theme-toggle]").forEach((el) => {
+ el.setAttribute("aria-label", label);
+ el.setAttribute("title", label);
+ });
+ };
+
+ const toggleTheme = () => {
+ const dark = document.body.classList.contains("dark");
+ if (dark) {
+ document.body.classList.remove("dark");
+ localStorage.setItem("pref-theme", "light");
+ } else {
+ document.body.classList.add("dark");
+ localStorage.setItem("pref-theme", "dark");
+ }
+ updateThemeUi();
+ };
+
+ const pref = localStorage.getItem("pref-theme");
+ if (pref === "dark") {
+ document.body.classList.add("dark");
+ } else if (pref === "light") {
+ document.body.classList.remove("dark");
+ }
+
+ document.querySelectorAll("[data-cn1-theme-toggle]").forEach((button) => {
+ button.addEventListener("click", toggleTheme);
+ });
+
+ const observer = new MutationObserver(updateThemeUi);
+ observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
+ updateThemeUi();
+
+ const scheme = window.matchMedia("(prefers-color-scheme: dark)");
+ if (scheme.addEventListener) {
+ scheme.addEventListener("change", updateThemeUi);
+ } else if (scheme.addListener) {
+ scheme.addListener(updateThemeUi);
+ }
+
+ document.querySelectorAll("[data-cn1-tabs]").forEach((tabs) => {
+ const triggers = Array.from(tabs.querySelectorAll("[data-cn1-tab-trigger]"));
+ if (!triggers.length) return;
+ const scope = tabs.closest("article, section, main, body") || document;
+ const panels = triggers
+ .map((trigger) =>
+ scope.querySelector(
+ `[data-cn1-tab-panel="${trigger.getAttribute("data-cn1-tab-trigger")}"]`
+ )
+ )
+ .filter(Boolean);
+
+ const setActive = (name) => {
+ triggers.forEach((trigger) => {
+ const active = trigger.getAttribute("data-cn1-tab-trigger") === name;
+ trigger.classList.toggle("is-active", active);
+ });
+ panels.forEach((panel) => {
+ const active = panel.getAttribute("data-cn1-tab-panel") === name;
+ panel.classList.toggle("is-active", active);
+ panel.hidden = !active;
+ });
+ };
+
+ triggers.forEach((trigger) => {
+ trigger.addEventListener("click", () => {
+ setActive(trigger.getAttribute("data-cn1-tab-trigger"));
+ });
+ });
+
+ const initial = tabs.querySelector("[data-cn1-tab-trigger].is-active");
+ setActive(
+ (initial && initial.getAttribute("data-cn1-tab-trigger")) ||
+ triggers[0].getAttribute("data-cn1-tab-trigger")
+ );
+ });
+
+})();
diff --git a/docs/website/content/about-us.md b/docs/website/content/about-us.md
new file mode 100644
index 0000000000..e50c977467
--- /dev/null
+++ b/docs/website/content/about-us.md
@@ -0,0 +1,51 @@
+---
+title: "About Us - What is Codename One and how Does it work?"
+date: 2019-05-18
+slug: "about-us"
+---
+
+Learn more about Codename One and getting started resources.
+
+### What is Codename One?
+
+Codename One allows Java developers to write their mobile apps using Java or Kotlin. It generates native OS binaries that you can upload to Apple/Google/Microsoft etc.
+
+Codename One was founded by ex-Sun/Oracle mobile developers based on an open source project started back in 2007 within Sun Microsystems. The core vision of Codename One is to actualize the WORA (Write Once Run Anywhere) mantra of Java in the age of mobile devices.
+
+To enable that vision Codename One created a one of a kind industry leading set of tools that allow Java developers to build native mobile OS applications with a single code base.
+
+Codename One works with all major Java developer environments (IDE's) NetBeans, Eclipse, IntelliJ/IDEA or VSCode.
+
+https://youtu.be/rl6z7DD2-vg
+
+### How does Codename One work?
+
+To understand how Codename One works, its history etc. check the [developer guide](/manual/) (also available as a [PDF](/files/developer-guide.pdf)). It provide a birds eye view in the first chapter.
+
+You can also refer to [this stackoverflow answer](http://stackoverflow.com/questions/10639766/how-codename-one-works/10646336). There is a video that goes into further details [here](https://www.youtube.com/watch?v=MrwbpdMALig) (it's a bit long).
+
+The gist of this is that Codename One requires you to [obey some restrictions](/blog/why-we-dont-support-the-full-java-api.html) in the way you develop Java applications and use our plugin/API. Once you abide by those the Codename One tooling makes portability seamless.
+
+### How do Codename One Apps Look?
+
+They can look like anything, check out the [demos](/demos.html) section where you can see the diversity and some of the potential of Codename One apps.
+
+### Is Codename One Free?
+
+Codename One has a free version and paid versions. So yes & no.
+
+The base product is open source but our build system requires servers (Macs, Windows machines etc.) and we place a quota on their usage to prevent overuse which would deprive paying users from service.
+
+With the free version, there is a JAR size limit of 1 - 8.5mb. Notice that this is a very high limit as it applies only to your bytcode and not to the Codename One builtin API's and you can create pretty remarkable apps within this limit (all our demos fit within that limit).
+
+The free tier allows you to use your generated apps commercially and place have no restrictions on them. There are no nag screens or license limitations!
+
+There are many additional benefits and additional features you can get in the paid plans, see the [pricing details here](https://www.codenameone.com/pricing.html).
+
+### Where can I learn more?
+
+For more about Codename One [check out this post](/blog/tutorials-resources-learn-java-mobile-videos-courses-ios-android.html) covering the extensive set of tutorials/docs/videos.
+
+You can ask questions in [stackoverflow with the codenameone tag](http://stackoverflow.com/tags/codenameone) or you can also ask in the [discussion forum](/discussion-forum.html). Our engineers guarantee a response within 24 hours for such questions.
+
+Notice that Codename One assumes you already know Java so we don't have any tutorials for complete beginners. However, we do have some courses available at [Codename One Academy](https://codenameone.teachable.com/).
diff --git a/docs/website/content/accepting-payments-with-zooz.md b/docs/website/content/accepting-payments-with-zooz.md
new file mode 100644
index 0000000000..dca6c871f0
--- /dev/null
+++ b/docs/website/content/accepting-payments-with-zooz.md
@@ -0,0 +1,15 @@
+---
+title: "Accepting Payments With Zooz"
+date: 2015-03-03
+slug: "accepting-payments-with-zooz"
+---
+
+# Accepting Payments With Zooz
+
+1. [Home](/)
+2. [Monetization](/monetization.html)
+3. Accepting Payments With Zooz
+
+To integrate with Zooz you need to register with them here and create a new application in their console. Make sure to create separate applications for iOS and Android since the keys can't be reused between the platforms. We will try to add HTML5 support for Blackberry in a future update.
+
+Once you did that the Codename One Payment API will seamlessly work with Zooz which is defined as a Manual payment provider and allows you to pay a fixed amount in a given currency.
diff --git a/docs/website/content/advanced-build.md b/docs/website/content/advanced-build.md
new file mode 100644
index 0000000000..a6128eeb96
--- /dev/null
+++ b/docs/website/content/advanced-build.md
@@ -0,0 +1,21 @@
+---
+title: "Advanced Build"
+date: 2015-03-03
+slug: "advanced-build"
+---
+
+# Advanced Build
+
+Build hints/arguments allow us to manipulate the build process on the build servers
+
+1. [Home](/)
+2. [Developers](https://beta.codenameone.com/getting-started.html)
+3. Advanced Build
+
+## Sending Build Settings To The Server
+
+
+
+When sending a build to the server you can provide additional parameters to the build which will be incorporated into the build process on the server to hint on multiple different build time options.You can do this by right clicking the Codename One project and entering the project properties. There you will find the "Build Hints" tab in which you can just type the arguments and the appropriate values from the list below.
+
+We removed this page since it duplicates content from the developer guide and we should probably maintain only one **authoritative** list. The up to date list is [here](https://www.codenameone.com/manual/advanced-topics.html#_sending_arguments_to_the_build_server).
diff --git a/docs/website/content/api.md b/docs/website/content/api.md
new file mode 100644
index 0000000000..b1a7e3fefb
--- /dev/null
+++ b/docs/website/content/api.md
@@ -0,0 +1,19 @@
+---
+title: "API"
+date: 2015-03-03
+slug: "api"
+---
+
+# The Codename One API
+
+1. [Home](/)
+2. Developers
+3. API
+
+The Codename One Open Source API is a vast abstraction of mobile device platforms providing rich portable functionality. It leverages the highest feature set within devices (nicknamed: highest common denominator) to provide functionality smartphone owners are accustomed to.
+
+The API is mature and operator tested on a global scale, yet it is still rapidly evolving to meet the changing demands of the global market. You can browse the [javadoc API here](/javadoc/index.html), to get a sense of its depth and breadth.
+
+The API includes tremendous amount of functionality including rich and infinitely customizable UI components, as well as all device features from calls to camera, GPS etc.
+
+No API can cover all the bases all the time, which is why we provide native interfaces that allow you to embed any arbitrary operating system component or code right in the middle of any Codename One application. You can write native code in the platform specific language (Dalvik-Java for Android, Objective C for iOS etc.) and use that code to create a platform specific widget that can be embedded right into the Codename One component hierarchy without interfering with application flow.
diff --git a/docs/website/content/app-gallery.md b/docs/website/content/app-gallery.md
new file mode 100644
index 0000000000..858c20d8ed
--- /dev/null
+++ b/docs/website/content/app-gallery.md
@@ -0,0 +1,15 @@
+---
+title: "App Gallery"
+date: 2020-09-22
+slug: "app-gallery"
+---
+
+## coming Soon...
+
+## App Gallery
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+Ut elit tellus, luctus nec ullamcorper mattis,
+pulvinar dapibus leo.
+
+Go Back to Home page 
diff --git a/docs/website/content/architecture-of-the-gui-builder.md b/docs/website/content/architecture-of-the-gui-builder.md
new file mode 100644
index 0000000000..a2d46d8d70
--- /dev/null
+++ b/docs/website/content/architecture-of-the-gui-builder.md
@@ -0,0 +1,91 @@
+---
+title: "Architecture Of The GUI Builder"
+date: 2015-03-03
+slug: "architecture-of-the-gui-builder"
+---
+
+# Architecture Of The GUI Builder
+
+1. [Home](/)
+2. Developers
+3. Architecture Of The GUI Builder
+
+The Codename One GUI builder has several unique underlying concepts that aren't as common among such tools, in this article I will try to clarify some of these basic ideas.
+
+### Basic Concepts
+
+The Codename One Designer isn't a standard code generator, the UI is saved within the resource file and can be designed without the source files available. This has several advantages:
+
+1. No fragile generated code to break.
+2. Designers who don't know Java can use the tool.
+3. The "[Codename One LIVE!](http://www.codenameone.com/codename-one-live.html)" application can show a live preview of your design as you build it.
+4. Images and theme settings can be integrated directly with the GUI without concern.
+5. The tool is consistent since the file you save is the file you run.
+6. GUI's/themes can be downloaded dynamically without replacing the application (this can reduce download size).
+7. It allows for control over application flow. It allows preview within the tool without compilation.
+
+This does present some disadvantages and oddities:
+
+1. Its harder to integrate custom code into the GUI builder/designer tool.
+2. The tool is somewhat opaque, there is no "code" you can inspect to see what was accomplished by the tool.
+3. If the resource file grows too large it can significantly impact memory/performance of a running application.
+4. Binding between code and GUI isn't as intuitive and is mostly centralized in a single class.
+
+In theory you don't need to generate any code, you can load any resource file that contains a UI element as you would normally load a Resource file:
+
+```
+Resources r = Resources.open("/myFile.res");
+```
+
+Then you can just create a UI using the UIBuilder API:
+
+```
+UIBuilder u = new UIBuilder();
+Container c = u.createContainer(r, "uiNameInResource");
+```
+
+(Notice that since Form & Dialog both derive from Container you can just downcast to the appropriate type).
+
+This would work for any resource file and can work completely dynamically! E.g. you can download a resource file on the fly and just show the UI that is within the resource file... That is what [Codename One LIVE!](http://www.codenameone.com/codename-one-live.html) is doing internally.
+
+### IDE Bindings
+
+While the option of creating a Resource file manually is powerful, its not nearly as convenient as modern GUI builders allow. Developers expect the ability to override events and basic behavior directly from the GUI builder and in mobile applications even the flow for some cases.
+
+To facilitate IDE integration we decided on using a single Statemachine class, similar to the common controller pattern. We considered multiple classes for every form/dialog/container and eventually decided this would make code generation more cumbersome.
+
+The designer effectively generates one class "StatemachineBase" which is a subclass of UIBuilder (you can change the name/package of the class in the Codename One properties file at the root of the project). StatemachineBase is generated every time the resource file is saved assuming that the resource file is within the src directory of a Codename One project. Since the state machine base class is always generated, all changes made into it will be overwritten without prompting the user.
+
+User code is placed within the Statemachine class, which is a subclass of the Statemachine Base class. Hence it is a subclass of UIBuilder!
+
+When the resource file is saved the designer generates 2 major types of methods into Statemachine base:
+
+1. Finders - findX(Container c). A shortcut method to find a component instance within a hierarchy of containers. Effectively this is a shortcut syntax for [UIBuilder.findByName()](/javadoc/com/codename1/ui/util/UIBuilder.html#findByName%28java.lang.String,%20com.codename1.ui.Container%29), its still useful since the method is type safe. Hence if a resource component name is changed the find() method will fail in subsequent compilations.
+2. Callback events - these are various callback methods with common names e.g.: onCreateFormX(), beforeFormX() etc. These will be invoked when a particular event/behavior occurs.
+
+ Within the GUI builder, the event buttons would be enabled and the GUI builder provides a quick and dirty way to just override these methods. To prevent a future case in which the underlying resource file will be changed (e.g formX could be renamed to formY) a super method is invoked e.g. super.onCreateFormX();
+
+ This will probably be replaced with the @Override annotation when Java 5 features are integrated into Codename One.
+
+### Working With The Generated Code
+
+The generated code is rather simplistic, e.g. the following code from the tzone demo adds a for the remove button toggle:
+
+
+
+As you can see from the code above implementing some basic callbacks within the state machine is rather simple. The method findFriendsRoot(c.getParent()); is used to find the "FriendsRoot" component within the hierarchy, notice that we just pass the parent container to the finder method. If the finder method doesn't find the friend root under the parent it will find the "true" root component and search there. The friends root is a container that contains the full list of our "friends" and within it we can just work with the components that were instantiated by the GUI builder. Implementing Custom Components There are two basic approaches for custom components:
+
+1. Override a specific type - e.g. make all Form's derive a common base class.
+2. Replace a deployed instance.
+
+The first uses a feature of UIBuilder which allows overriding component types, specifically override [createComponentInstance](/javadoc/com/codename1/ui/util/UIBuilder.html#createComponentInstance%28java.lang.String,%20java.lang.Class%29) to return an instance of your desired component e.g.:
+
+
+
+This code allows me to create a unified global form subclass. That's very useful when I want so global system level functionality that isn't supported by the designer normally.
+
+The second approach allows me to replace an existing component:
+
+
+
+Notice that we replace the title with an empty label, in this case we do this so we can later replace it while animating the replace behavior thus creating a slide-in effect within the title. It can be replaced though, for every purpose including the purpose of a completely different custom made component. By using the replace method the existing layout constraints are automatically maintained.
diff --git a/docs/website/content/archived-builds.md b/docs/website/content/archived-builds.md
new file mode 100644
index 0000000000..836aec9316
--- /dev/null
+++ b/docs/website/content/archived-builds.md
@@ -0,0 +1,13 @@
+---
+title: "Archived Builds"
+date: 2015-03-03
+slug: "archived-builds"
+---
+
+# Archived Builds
+
+1. [Home](/)
+2. [Developers](https://beta.codenameone.com/getting-started.html)
+3. Archived Builds
+
+This functionality was deprecated in recent versions and is no longer supported
diff --git a/docs/website/content/blog/3-image-tools-for-app-marketing.md b/docs/website/content/blog/3-image-tools-for-app-marketing.md
new file mode 100644
index 0000000000..c065d21cbe
--- /dev/null
+++ b/docs/website/content/blog/3-image-tools-for-app-marketing.md
@@ -0,0 +1,89 @@
+---
+title: 3 Image Tools for App Marketing
+slug: 3-image-tools-for-app-marketing
+url: /blog/3-image-tools-for-app-marketing/
+original_url: https://www.codenameone.com/blog/3-image-tools-for-app-marketing.html
+aliases:
+- /blog/3-image-tools-for-app-marketing.html
+date: '2016-09-05'
+author: Shai Almog
+---
+
+
+
+Every now and again developers ask us how we do the graphics for our posts/promotions and up until recently the
+answer was “photoshop”. While knowing photoshop is still very worthwhile we still like these 3 tools that provide great
+shortcuts to creating both screenshots and art.
+
+### The App Launchpad
+
+[App Launchpad](http://theapplaunchpad.com/) is a newcomer to the scene, it’s still a new tool at the MVP stage.
+The potential is still great and it generates reasonably attractive graphics such as these:
+
+[](/img/blog/app-launchpad.png)
+
+Figure 1. App launchpad images contain a device frame with text/colors
+
+The UX for the tool is reasonably intuitive and you can work on several screenshots at once. There are some missing
+basic features that I’d like to see moving forward but the tool is already pretty useful. E.g. we used it for the kitchensink
+screenshots on [Android](https://play.google.com/store/apps/details?id=com.codename1.demos.kitchen) and
+[iOS](https://itunes.apple.com/us/app/kitchen-sink-codename-one/id635048865).
+
+Hopefully they will improve the tool by adding:
+
+ * Landscape support
+
+ * Tablet skins
+
+ * Thin fonts
+
+ * More customization abilities e.g. image/texture backgrounds
+
+### Smart Mockups
+
+[Smart Mockups](http://smartmockups.com/) is a [PlaceIt](http://placeit.breezi.com/) clone that isn’t
+as aggressively monetized. It allows you to take a screenshot and place it within a context of a device held by a user.
+Normally this is slightly challenging to do in photoshop unless you have a ready made template.
+
+The image at the top of this post was generated with [Smart Mockups](http://smartmockups.com/) and Adobe Spark…
+
+### Adobe Spark
+
+[Spark](https://spark.adobe.com/) is a tool that makes typography easy. It does quite a few things to generate images
+such as the image above that mix image filters, typography, shape decoration etc.
+
+All of those allow you to create meme style images as well as a wide variety of banners/promo images that you
+can use to market your apps in the stores.
+
+[](/img/blog/image-tools-for-app-marketing.jpg)
+
+Figure 2. Adobe Spark & Smart Mockups were used to create this image
+
+### Good Looking Graphics
+
+The app icon and screenshots are where the initial opinion of your app is formed. If the promotional material isn’t
+good some users will still give your app the benefit of the doubt, but they will do so with a negative first impression.
+
+Good promotional material is no substitute for good app design, but it’s a step that you need to take.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Chris Moore** — May 30, 2019 at 7:49 am ([permalink](https://www.codenameone.com/blog/3-image-tools-for-app-marketing.html#comment-24041))
+
+> Hey Shai!
+>
+> Nice tips. I wonder whether you’ve tried Promomatic ([https://www.promomatic.com/)]()) for building app screenshots instead of AppLaunchPad? Super easy to use and cost effective when building for the app store launch. Found them on Product Hunt a few months back.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2F3-image-tools-for-app-marketing.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/5-cool-new-features-in-codename-one-for-netbeans-ide.md b/docs/website/content/blog/5-cool-new-features-in-codename-one-for-netbeans-ide.md
new file mode 100644
index 0000000000..ee18860520
--- /dev/null
+++ b/docs/website/content/blog/5-cool-new-features-in-codename-one-for-netbeans-ide.md
@@ -0,0 +1,39 @@
+---
+title: 5 Cool New Features in Codename One for NetBeans IDE
+slug: 5-cool-new-features-in-codename-one-for-netbeans-ide
+url: /blog/5-cool-new-features-in-codename-one-for-netbeans-ide/
+original_url: https://www.codenameone.com/blog/5-cool-new-features-in-codename-one-for-netbeans-ide.html
+aliases:
+- /blog/5-cool-new-features-in-codename-one-for-netbeans-ide.html
+date: '2014-01-19'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+An article of mine with the above title just
+[
+got published in dzone
+](http://netbeans.dzone.com/articles/5-cool-new-features-codename)
+check it out and let us know what you think in the comments over there.
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/5-tips-for-gamifying-your-mobile-app.md b/docs/website/content/blog/5-tips-for-gamifying-your-mobile-app.md
new file mode 100644
index 0000000000..54e367c3db
--- /dev/null
+++ b/docs/website/content/blog/5-tips-for-gamifying-your-mobile-app.md
@@ -0,0 +1,130 @@
+---
+title: 5 Tips for Gamifying Your Mobile App
+slug: 5-tips-for-gamifying-your-mobile-app
+url: /blog/5-tips-for-gamifying-your-mobile-app/
+original_url: https://www.codenameone.com/blog/5-tips-for-gamifying-your-mobile-app.html
+aliases:
+- /blog/5-tips-for-gamifying-your-mobile-app.html
+date: '2013-03-23'
+author: Shai Almog
+---
+
+
+
+This is a guest post by Yaniv Nizan who is the CEO and Co-Founder of
+[
+The SOOMLA Project
+](http://soom.la/)
+, the platform for Creating In-App Purchase Stores for Mobile Games. Yaniv also writes in 4 different blogs including
+[
+blog.soom.la
+](http://blog.soom.la/)
+, speaks in different industry events about gamification and game design and tweets
+[
+@y_nizan
+](http://twitter.com/y_nizan)
+.
+
+* * *
+
+> Gamification is the practice of using game mechanics in a different context with a goal to engage users
+>
+
+Mobile apps are in fierce competition these days. There are over one million apps available in the different marketplaces and while a user may install a large number of apps on his device. Recent research by Flurry shows that users don’t use many apps for very long and in fact only 25% of the apps survive after 3 months.
+
+[
+
+](/img/blog/old_posts/5-tips-for-gamifying-your-mobile-app-large-3.png)
+
+This is one of the main reasons why app developers need to invest a lot of resources in engaging the users and increasing the average length of activity for the users. One interesting way of doing that is through the user of gamification. Games are very popular in smartphones and most users are already accustomed to playing games on their mobile devices so the fit seems very natural here.
+
+Digging a bit deeper into gamification, there are a few basic game mechanics that are suited for gamifying most mobile apps: Goals or Achievements, Problem Solving and Awards. I’m deliberately leaving social out since it is not a purely game related. It is present in some games and in some apps. On top of the game mechanics gamification is also about making elements more responsive and providing instant gratification. These are easier to do on touch devices but not all apps are utilizing the power of this approach.
+
+So now that we know what Gamification is, let’s define the goal in the context of a mobile app. The idea here is to get the user in the engagement loop. The engagement loop is achieved when the user invests time in order to improve his value from the application and then he is willing to invest more time which will increase the value and so forth. The more iterations of this loop, the better the
+[
+app engagement
+](http://soom.la)
+will be.
+
+[
+
+](/img/blog/old_posts/5-tips-for-gamifying-your-mobile-app-large-4.png)
+
+Here is how we can combine all of these together:
+
+
+
+## Tip 1 – Creating Engagement Opportunities for the User
+
+This is a key component in creating the engagement loop. You app needs to have elements that allow users to invest time and get more out of the app. One easy example for this is profile building. In apps that provide social interaction, investing time in your profile is not a required step but the more a user invests time in it, the better his experience will be. Same goes for apps that allow you to import your friends from other social networks. The more time you invest, the more engaging the app is likely to be for you.
+
+## Tip 2 – Encouraging Your Users to Invest the Time through Achievements
+
+While the user does get value from investing his time, he doesn’t get it right away. This is part of the reason why it’s important to provide a more instant gratification even for an action that is good for the user. NOTE – it’s important not to provide short term rewards for actions that will end up hurting the user – this can backfire and produce a negative result. Easy elements that can be added to encourage users are progress bar for completing his user profile or awards for connecting and inviting friends.
+
+## Tip 3 – Guide Users about the Value They Can Receive
+
+Once your app users invested time and unlocked more value. You can add responsive UX that guides them about opportunities to get more value as they appear. This could be anything from friend recommendations to relaying on the app for day to day tasks. If we take one example, a task management app can encourage users to connect the app with the calendar app through achievements and awards. Once the calendar app is connected, the task management app can recommend the user to add tasks after a meeting.
+
+## Tip 4 – Getting Rid of Clutter via Smart Unlocking
+
+While great apps allow users to get tremendous value, they also need to be very simple. The conflict here is that the more functionality you add to the app the more it is becoming more complicated. Luckily, gamification does provide a solution. Provided that you are already maintaining some score about the user, you can gradually unlock features based on his score or level. This approach allows you to keep the first experience simple and gradually add more value later on.
+
+## Tip 5 – Reward your Key Users
+
+Every successful app has users that evangelize the app and explain others how to get value from it. While you can design a great UX with guidance and tutorials, nothing beats a friend explaining the user how to use the app and showing him the end result. This is not about getting buzz by soliciting likes on Facebook. This is about finding the few that are really excited about the app and gamifying their evangelism activity. You could have awards such as the Educator, the Trainer and you could reward users for repeatedly mentioning your app.
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Anonymous** — March 24, 2013 at 2:56 pm ([permalink](https://www.codenameone.com/blog/5-tips-for-gamifying-your-mobile-app.html#comment-24251))
+
+> Anonymous says:
+>
+> Great post guys!
+>
+> Keep up the good work!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2F5-tips-for-gamifying-your-mobile-app.html)
+
+
+### **Anonymous** — March 24, 2013 at 4:20 pm ([permalink](https://www.codenameone.com/blog/5-tips-for-gamifying-your-mobile-app.html#comment-21425))
+
+> Anonymous says:
+>
+> Very cool. Thanks for the post!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2F5-tips-for-gamifying-your-mobile-app.html)
+
+
+### **Anonymous** — November 13, 2013 at 10:25 am ([permalink](https://www.codenameone.com/blog/5-tips-for-gamifying-your-mobile-app.html#comment-21681))
+
+> Anonymous says:
+>
+> Thanks for this post. Do you have any examples of utility apps that do this well? Eg, Telecoms, banking, power etc
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2F5-tips-for-gamifying-your-mobile-app.html)
+
+
+### **Balbir singh** — February 7, 2018 at 10:16 am ([permalink](https://www.codenameone.com/blog/5-tips-for-gamifying-your-mobile-app.html#comment-23869))
+
+> Balbir singh says:
+>
+> thanks for sharing this informative post about mobile app.
+> [http://www.nanoarchsoftware…]()
+> .
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2F5-tips-for-gamifying-your-mobile-app.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/64-bit-oss-vm.md b/docs/website/content/blog/64-bit-oss-vm.md
new file mode 100644
index 0000000000..d5fb36df84
--- /dev/null
+++ b/docs/website/content/blog/64-bit-oss-vm.md
@@ -0,0 +1,82 @@
+---
+title: 64 bit & OSS VM
+slug: 64-bit-oss-vm
+url: /blog/64-bit-oss-vm/
+original_url: https://www.codenameone.com/blog/64-bit-oss-vm.html
+aliases:
+- /blog/64-bit-oss-vm.html
+date: '2014-10-19'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+Apple Just announced they will mandate 64 bit for all new submissions starting February 2015, luckily we are very prepared for that and we already support 64 bit builds in our new VM.
+
+This is a huge triumph for the architecture of our new VM which is very resilient to changes Apple might make in the future thanks to its pure C architecture. Our old XMLVM based approach is limited in that regard since it relies on the Boehm garbage collector which in turn relies on low level assembler and memory layouts heavily. Our new VM is both faster and more portable; now its also open source!
+
+In fact, the only portion that posed a slight challenge was porting the native libzbar which we use for QR/barcode reading. Everything related to the VM itself ported easily and works with the new high resolution devices as well as arm64.
+
+If you want to try your app with the new VM just submit a build with the build argument ios.newVM=true. We are now actively seeking bug reports, crashes and compilation errors with the new VM in order to be able to transition it into the default VM by January. If you get an error please reproduce it as a standalone project we can compile and attach it to an issue in the issue tracker describing the problem.
+
+We just committed the current beta of the new VM into SVN, its still not production grade but its now open source under the GPL + Class Path Exception. You can see the source under the VM directory of our SVN tree where you should see two projects. One is the bytecode translator and the other is the Java API implementation.
+
+Basically the architecture is very simple, it statically parses the bytecode using ASM and generates C source/header files that pretty much represent the bytecode as it is in Java. It has a rudimentary optimizer but nothing too shabby.
+
+The main focus in the future would be removing some of the stack operations in the bytecode by possibly converting the bytecode on the fly to something that would be more efficient on the device. We will probably stay with the stack based approach rather than go completely register based like DEX has for two major reasons:
+
+1\. Similarity to the source bytecode which makes it relatively simple to locate the relevant section.
+
+2\. The stack based approach is very powerful when combined with our GC so we will need it for the GC to work correctly.
+
+On a slightly unrelated note, IAP version 3.0 is now up on the build servers. We made a few corrections to the post from last week containing a discussion of the changes needed so if you use it please
+[
+check out the revised post
+](http://www.codenameone.com/blog/migrating-to-androids-in-app-purchase-30)
+.
+
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Anonymous** — November 10, 2014 at 8:13 am ([permalink](https://www.codenameone.com/blog/64-bit-oss-vm.html#comment-21907))
+
+> Anonymous says:
+>
+> I got an error when I tried to build with the new vm
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2F64-bit-oss-vm.html)
+
+
+### **Anonymous** — November 26, 2014 at 8:14 am ([permalink](https://www.codenameone.com/blog/64-bit-oss-vm.html#comment-22175))
+
+> Anonymous says:
+>
+> Can you post on the forum with more details e.g. build error log?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2F64-bit-oss-vm.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/8-to-6.md b/docs/website/content/blog/8-to-6.md
new file mode 100644
index 0000000000..9a1b41d648
--- /dev/null
+++ b/docs/website/content/blog/8-to-6.md
@@ -0,0 +1,98 @@
+---
+title: 8 to 6+
+slug: 8-to-6
+url: /blog/8-to-6/
+original_url: https://www.codenameone.com/blog/8-to-6.html
+aliases:
+- /blog/8-to-6.html
+date: '2014-09-14'
+author: Shai Almog
+---
+
+
+
+
+
+
+[
+
+](/img/blog/old_posts/8-to-6-large-2.png)
+
+
+
+iOS 8 and iPhone 6/6+ are nearly upon us and Codename One is ready for both!
+
+
+
+
+We’ve just added device skins for iOS 6 and 6+, notice that due to the very high resolution you might want to disable scrolling. Notice that those skins are ridiculously large and as such might trigger an out of memory error when you try to run. To fix this just go to the project properties and select the run section. In it select the VM options and configure -Xmx128m or even higher.
+
+
+
+
+
+
+The 6+ device will use the HD level DPI so you should make sure to have graphics at that resolutions in order to truly make use of that device.
+
+
+The build servers will now take
+[
+10 screenshots instead of 7
+](http://www.codenameone.com/3/post/2014/03/the-7-screenshots-of-ios.html)
+for iOS devices, these include the 6 the 6+ and the landscape version of 6+.
+
+
+
+
+The file names are:
+
+
+[[email protected]](/cdn-cgi/l/email-protection) – 750 x 1334
+
+
+
+[[email protected]](/cdn-cgi/l/email-protection)
+
+– 1242 x 2208
+
+
+[[email protected]](/cdn-cgi/l/email-protection) – 2208 x 1242
+
+
+
+
+You will notice that on stage Apple claimed that the device is 1080p which is true, but graphics for it is raised by a multiple of 3 then downscaled by hardware to simplify the programming model.
+
+
+
+
+We are also running the latest gold master version of iOS 8 which seems to support Codename One applications just as well as iOS 7 without any changes required.
+
+
+
+
+We’ll write more about the JavaZone conference trip soon.
+
+
+
+
+
+
+
+
+
+
+
+
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/_index.md b/docs/website/content/blog/_index.md
new file mode 100644
index 0000000000..7319f57e7c
--- /dev/null
+++ b/docs/website/content/blog/_index.md
@@ -0,0 +1,11 @@
+---
+title: "Blog"
+description: "Insights, release updates, tutorials, and engineering notes from the Codename One team."
+ShowToc: false
+outputs:
+ - HTML
+ - RSS
+ - Feed
+---
+
+Latest posts from Codename One.
diff --git a/docs/website/content/blog/a-junior-software-developers-journey-at-codename-one.md b/docs/website/content/blog/a-junior-software-developers-journey-at-codename-one.md
new file mode 100644
index 0000000000..1b5a73661f
--- /dev/null
+++ b/docs/website/content/blog/a-junior-software-developers-journey-at-codename-one.md
@@ -0,0 +1,96 @@
+---
+title: A Junior Software Developer’s Journey at Codename One
+slug: a-junior-software-developers-journey-at-codename-one
+url: /blog/a-junior-software-developers-journey-at-codename-one/
+original_url: https://www.codenameone.com/blog/a-junior-software-developers-journey-at-codename-one.html
+aliases:
+- /blog/a-junior-software-developers-journey-at-codename-one.html
+date: '2020-12-07'
+author: Sergey Gerashenko
+---
+
+Hello Codename One community, my name is Sergey Gerashenko. I’m a junior software developer at Codename One. My journey here began 3 months ago when I passed my first interview at Codename One and my first task was to learn Java within 2 weeks.
+
+### Learning Java
+
+I knew a few programming languages like Python, C and C++ but was unfamiliar with Java or managed languages beforehand.
+
+Learning a completely new language in just two weeks seemed like an impossible task at first because it took me around five months to learn C++.
+
+I joined this [Java course](https://www.udemy.com/course/java-the-complete-java-developer-course) on Udemy and started learning immediately.
+
+My first impression of Java was great. It reminded me of C++ but much easier to understand and work with. After two weeks I felt confident with Java and started my work at Codename One.
+
+My first task was to update the Kitchen Sink cross-platform demo application to demonstrate all the main usages of Codename One framework.
+
+### The First Week
+
+My first week at the new job was a mess. The problem was that I came from the Real Time and Embedded background. I had never used Codename One or any other UI tools before and had a knowledge gap.
+
+I tried learning by watching tutorial videos on [Codename One Academy](https://codenameone.teachable.com/) but it was a bit difficult for someone without UI development experience.
+
+In the beginning, I didn’t understand some common terms and jargons and I had to Google my way around in order to get a good grasp of things. At the end of the first week, I was asked to show my progress.
+
+I barely wrote 100 lines of code (not the best ones). Even worst than that, I felt like I didn’t learn enough in the past week. In the second week, I decided to start from scratch. Now that I knew the basics, things became much easier.
+
+### What Really Helped?
+
+I found the [Codename One developer guide](https://www.codenameone.com/developer-guide.html) to be the best learning resource. It’s great for learning your way around the tool and understanding its components.
+
+In the [How Do I](https://www.codenameone.com/how-do-i.html) section I found some very helpful video tutorials that also helped a lot with learning common use cases, like the [Layout basics](https://www.codenameone.com/how_di_i/how-do-i-positioning-components-using-layout-managers.html) video, and the [How to convert a PSD Design into a Native Mobile App](https://www.codenameone.com/video/how-to-convert-a-psd-design-into-a-native-mobile-app.html).
+
+At that point, I was comfortable enough with a good foundation level knowledge and my work seemed much smoother and stress-free.
+
+### Learning Kotlin
+
+I started to enjoy the process of building apps, and everything else in between. When my app was ready, I was asked to write the [Kitchen Sink in Kotlin](https://github.com/codenameone/KitchenSinkKotlin) to demonstrate our Kotlin support.
+
+My first impression of Kotlin wasn’t that great just as my first impression of Java. Kotlin syntax was a little bit different from those languages that I knew so far, but when I studied it more I started to see its real beauty.
+
+Kotlin reminds me of Java but it seems more concise and readable after you get used to it. I spent the next two weeks on learning and writing the same Kitchen Sink app in Kotlin.
+
+The process wasn’t too hard as Kotlin has great tools to convert Java code to Kotlin. Though it was far from perfect and I spent a lot of time to review the code and make the necessary changes.
+
+After two months of hard work, learning and just enjoying the process, my app was ready for deployment and I was ready for my next challenge.
+
+### Kitchen Sink
+
+The Codename One [Kitchen Sink](https://play.google.com/store/apps/details?id=com.codename1.demos.kitchen) app is a great tool for those who want to start developing applications with Codename One tools.
+
+It explains all the basic components of Codename One and demonstrates the most common uses of them. It also demonstrates more complicated uses of Codename One like the low-level graphics possibilities with a gorgeous Clock demo and the use of GoogleMaps lib.
+
+And even better, the app contains links to the [source code](https://github.com/codenameone/KitchenSink) of the project, so anyone can just download the code, see how they can use specific components, play with them and experiment.
+
+But more importantly, users can see and learn about the application structure and how it needs to be written.
+
+
+
+
+
+
+
+
+
+
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **[email protected]** — December 26, 2020 at 2:56 pm ([permalink](https://www.codenameone.com/blog/a-junior-software-developers-journey-at-codename-one.html#comment-24368))
+
+> [[email protected]](/cdn-cgi/l/email-protection) says:
+>
+> Looking forward to having an extra hand on CN1, Sergey. Welcome 🙂
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-junior-software-developers-journey-at-codename-one.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/a-new-idea.md b/docs/website/content/blog/a-new-idea.md
new file mode 100644
index 0000000000..f502596e98
--- /dev/null
+++ b/docs/website/content/blog/a-new-idea.md
@@ -0,0 +1,285 @@
+---
+title: A New Idea
+slug: a-new-idea
+url: /blog/a-new-idea/
+original_url: https://www.codenameone.com/blog/a-new-idea.html
+aliases:
+- /blog/a-new-idea.html
+date: '2016-04-05'
+author: Shai Almog
+---
+
+
+
+Our current IntelliJ/IDEA plugin is seriously behind the times. In our recent survey it was very easy to spot
+IDEA developers who were significantly less satisfied with Codename One than the rest of the developer community.
+The IDEA plugin doesn’t include basic capabilities such as:
+
+ * Java 8 Support
+
+ * The New GUI builder
+
+ * New Default application look/themes/icon
+
+ * Builtin demo apps
+
+ * The Generate Native Access functionality
+
+ * The certificate wizard and a lot of the UI within the preferences
+
+### A New Direction
+
+The old plugin was developed by an excellent hacker who did a great job, but even the best code rots when
+it is unmaintained. Worse, Jetbrains made some big changes between version 12 and now. The old plugin couldn’t
+be compiled on the newer versions and exhibits odd bugs when running on newer versions of IDEA.
+
+We decided to rewrite the plugin from the ground up and discard a lot of the legacy both in terms of the plugin
+and in terms of Codename One functionality.
+
+The main goal of this rewrite is reduction in code so we can have a very lean plugin with as much shared code
+as possible. For that purpose the templates and builtin files are literally taken from the NetBeans plugin to facilitate
+as much reuse as possible and allow for one release cycle to encapsulate everything.
+
+#### Java 8+ IntelliJ/IDEA 16+
+
+The new plugin will only work on Java 8 or newer VM’s and will implicitly create Java 8 projects.
+
+Supporting legacy project structures doesn’t make sense for the new plugin although you could manually set that
+in the generated project, this is probably unnecessary.
+
+#### New Preferences
+
+This might be the most controversial decision we’ve made with this plugin. Instead of using the native IDE
+menu we chose to use the right click menu for a lot of features including our own preferences UI:
+
+
+
+Figure 1. The preferences as well as other options are in the right click menu instead of the native IDE menu
+
+This launches a custom preferences UI written using Codename One that looks like this:
+
+
+
+Figure 2. Preferences UI written in Codename One
+
+
+
+Figure 3. Preferences UI under the basics section
+
+The value of writing the preferences UI using the Codename One API is that it makes the maintenance of this
+code far easier when compared to IDE specific code. A lot of features aren’t mapped to UI within the plugins
+at this time because it’s just too much of a hassle to do this 3x times for every OS/platform. Right now we
+have an internal debate on whether this should be the approach we take for all platforms, in the long run I think
+this would be superior to using the IDE native preferences UI.
+
+The new preferences also allow us to integrate deeply with capabilities such as the iOS certificate wizard
+which has the best most fluent integration in IntelliJ/IDEA. Personally I think it looks modern and better than the
+IDE integrated approach.
+
+#### The New GUI Builder
+
+Support for the new GUI builder is builtin, as the builder is still in beta I’ll leave this as a somewhat “undocumented feature”
+until we finish that work. I would like to mention though that the integration injects code directly to the AST and
+dynamically updates it when the gui XML file is saved.
+
+Normally the GUI builder updates the sources via the ant task but we could find no way to do this in IntelliJ that
+didn’t break basic things.
+
+### Video
+
+I’ve made a quick walkthru video of the new plugin, check it out:
+
+### Migration & Availability
+
+While we belive this should be maintained across versions we can’t be 100% sure about this. As with all rewrites
+regressions probably exist. We will release the new plugin this Friday as part of our standard Friday release cycle.
+
+If you run into regressions we suggest creating a new project with the new plugin and migrating your code/settings
+by copying them to the new plugin. Be sure to let us know immediately if you run into such issues.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Therk** — April 13, 2016 at 11:42 pm ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22656))
+
+> Thank you for improving the plugin. It is more intuitive even though it may not be consistent with other IntelliJ plugins. Unfortunately, I started to get a NullPointerException when setting up the Run configuration for a new sample application.
+> I am not sure the best way to report such issues, but the stack trace is:
+>
+> java.lang.NullPointerException
+>
+> at com.codename1.plugin.intellij.run.CodenameOneRunConfigurationEditor.initComponent([CodenameOneRunConfiguration…]():31)
+>
+> at com.codename1.plugin.intellij.run.CodenameOneRunConfigurationEditor.([CodenameOneRunConfiguration…]():27)
+> at com.codename1.plugin.intellij.run.CodenameOneRunConfiguration.getConfigurationEditor([CodenameOneRunConfiguration…]():225)
+> at com.intellij.execution.impl.ConfigurationSettingsEditor.([ConfigurationSettingsEditor…]():220)
+> at com.intellij.execution.impl.ConfigurationSettingsEditorWrapper.([ConfigurationSettingsEditor…]():67)
+> at com.intellij.execution.impl.SingleConfigurationConfigurable.([SingleConfigurationConfigur…]():65)
+> at com.intellij.execution.impl.SingleConfigurationConfigurable.editSettings([SingleConfigurationConfigur…]():99)
+> at com.intellij.execution.impl.RunConfigurable.a([RunConfigurable.java]():1090)
+> at com.intellij.execution.impl.RunConfigurable.createNewConfiguration([RunConfigurable.java]():1123)
+> at com.intellij.execution.impl.RunConfigurable$MyToolbarAddAction$2.consume([RunConfigurable.java]():1161)
+> at com.intellij.execution.impl.RunConfigurable$MyToolbarAddAction$2.consume([RunConfigurable.java]():1158)
+> at com.intellij.execution.impl.NewRunConfigurationPopup$1.onChosen([NewRunConfigurationPopup.java]():82)
+> at com.intellij.execution.impl.NewRunConfigurationPopup$1.onChosen([NewRunConfigurationPopup.java]():48)
+> at com.intellij.ui.popup.list.ListPopupImpl.a([ListPopupImpl.java]():386)
+> at com.intellij.ui.popup.list.ListPopupImpl.handleSelect([ListPopupImpl.java]():346)
+> at com.intellij.ui.popup.list.ListPopupImpl$MyMouseListener.mouseReleased([ListPopupImpl.java]():476)
+> at java.awt.AWTEventMulticaster.mouseReleased([AWTEventMulticaster.java]():290)
+> at java.awt.Component.processMouseEvent([Component.java]():6535)
+> at javax.swing.JComponent.processMouseEvent([JComponent.java]():3324)
+> at com.intellij.ui.popup.list.ListPopupImpl$MyList.processMouseEvent([ListPopupImpl.java]():542)
+> at java.awt.Component.processEvent([Component.java]():6300)
+> at java.awt.Container.processEvent([Container.java]():2236)
+> at java.awt.Component.dispatchEventImpl([Component.java]():4891)
+> at java.awt.Container.dispatchEventImpl([Container.java]():2294)
+> at java.awt.Component.dispatchEvent([Component.java]():4713)
+> at java.awt.LightweightDispatcher.retargetMouseEvent([Container.java]():4888)
+> at java.awt.LightweightDispatcher.processMouseEvent([Container.java]():4525)
+> at java.awt.LightweightDispatcher.dispatchEvent([Container.java]():4466)
+> at java.awt.Container.dispatchEventImpl([Container.java]():2280)
+> at java.awt.Window.dispatchEventImpl([Window.java]():2750)
+> at java.awt.Component.dispatchEvent([Component.java]():4713)
+> at java.awt.EventQueue.dispatchEventImpl([EventQueue.java]():758)
+> at java.awt.EventQueue.access$500([EventQueue.java]():97)
+> at java.awt.EventQueue$[3.run](:709)
+> at java.awt.EventQueue$[3.run](:703)
+> at java.security.AccessController.doPrivileged(Native Method)
+> at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege([ProtectionDomain.java]():76)
+> at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege([ProtectionDomain.java]():86)
+> at java.awt.EventQueue$[4.run](:731)
+> at java.awt.EventQueue$[4.run](:729)
+> at java.security.AccessController.doPrivileged(Native Method)
+> at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege([ProtectionDomain.java]():76)
+> at java.awt.EventQueue.dispatchEvent([EventQueue.java]():728)
+> at com.intellij.ide.IdeEventQueue.h([IdeEventQueue.java]():857)
+> at com.intellij.ide.IdeEventQueue._dispatchEvent([IdeEventQueue.java]():654)
+> at com.intellij.ide.IdeEventQueue.dispatchEvent([IdeEventQueue.java]():386)
+> at java.awt.EventDispatchThread.pumpOneEventForFilters([EventDispatchThread.java]():201)
+> at java.awt.EventDispatchThread.pumpEventsForFilter([EventDispatchThread.java]():116)
+> at java.awt.EventDispatchThread.pumpEventsForFilter([EventDispatchThread.java]():109)
+> at java.awt.WaitDispatchSupport$[2.run](:184)
+> at java.awt.WaitDispatchSupport$[4.run](:229)
+> at java.awt.WaitDispatchSupport$[4.run](:227)
+> at java.security.AccessController.doPrivileged(Native Method)
+> at java.awt.WaitDispatchSupport.enter([WaitDispatchSupport.java]():227)
+> at [java.awt.Dialog.show](:1084)
+> at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl$[MyDialog.show](:792)
+> at [com.intellij.openapi.ui.imp…](:465)
+> at com.intellij.openapi.ui.DialogWrapper.invokeShow([DialogWrapper.java]():1661)
+> at [com.intellij.openapi.ui.Dia…](:1610)
+> at com.intellij.openapi.options.ex.SingleConfigurableEditor.access$001([SingleConfigurableEditor.java]():45)
+> at com.intellij.openapi.options.ex.SingleConfigurableEditor$[1.run](:130)
+> at com.intellij.openapi.project.DumbPermissionServiceImpl.allowStartingDumbModeInside([DumbPermissionServiceImpl.java]():31)
+> at com.intellij.openapi.project.DumbService.allowStartingDumbModeInside([DumbService.java]():283)
+> at [com.intellij.openapi.option…](:127)
+> at com.intellij.execution.impl.EditConfigurationsDialog.access$001([EditConfigurationsDialog.java]():33)
+> at com.intellij.execution.impl.EditConfigurationsDialog$[1.run](:57)
+> at com.intellij.openapi.project.DumbPermissionServiceImpl.allowStartingDumbModeInside([DumbPermissionServiceImpl.java]():37)
+> at com.intellij.openapi.project.DumbService.allowStartingDumbModeInside([DumbService.java]():283)
+> at [com.intellij.execution.impl…](:54)
+> at com.intellij.openapi.ui.DialogWrapper.showAndGet([DialogWrapper.java]():1625)
+> at com.intellij.execution.actions.ChooseRunConfigurationPopup$8.perform([ChooseRunConfigurationPopup…]():974)
+> at com.intellij.execution.actions.ChooseRunConfigurationPopup$ConfigurationListPopupStep$[2.run](…]():500)
+> at java.awt.event.InvocationEvent.dispatch([InvocationEvent.java]():311)
+> at java.awt.EventQueue.dispatchEventImpl([EventQueue.java]():756)
+> at java.awt.EventQueue.access$500([EventQueue.java]():97)
+> at java.awt.EventQueue$[3.run](:709)
+> at java.awt.EventQueue$[3.run](:703)
+> at java.security.AccessController.doPrivileged(Native Method)
+> at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege([ProtectionDomain.java]():76)
+> at java.awt.EventQueue.dispatchEvent([EventQueue.java]():726)
+> at com.intellij.ide.IdeEventQueue.h([IdeEventQueue.java]():857)
+> at com.intellij.ide.IdeEventQueue._dispatchEvent([IdeEventQueue.java]():658)
+> at com.intellij.ide.IdeEventQueue.dispatchEvent([IdeEventQueue.java]():386)
+> at java.awt.EventDispatchThread.pumpOneEventForFilters([EventDispatchThread.java]():201)
+> at java.awt.EventDispatchThread.pumpEventsForFilter([EventDispatchThread.java]():116)
+> at java.awt.EventDispatchThread.pumpEventsForHierarchy([EventDispatchThread.java]():105)
+> at java.awt.EventDispatchThread.pumpEvents([EventDispatchThread.java]():101)
+> at java.awt.EventDispatchThread.pumpEvents([EventDispatchThread.java]():93)
+> at [java.awt.EventDispatchThrea…](:82)
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+
+### **Therk** — April 14, 2016 at 12:21 am ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22484))
+
+> A few suggestion on improving the IDEA plugin:
+> 1\. In the Codename One Preferences, tabbing between fields does not seem to work, requiring a mouse click into next fields. For example in the Login or iOS Certificate Wizard.
+> 2\. It is unclear if user is logged in. It would be good to show login email under Login icon if login was successful.
+> 3\. Adding certificate, revokes existing certificate. It prompts “Your iTunes account already has an iOS appstore certificate. Would you like to overwrite it?”. If I click ‘No’, then no certificate is used. It would also help to know that old certificate will be revoked in the message.
+> 4\. The iOS Certificate Wizard, tried to create certificate for each device.
+> 5\. In the device list of iOS Certificate Wizard, showing Identifier would be helpful.
+> 6\. It should allow to select existing App ID and name for an application.
+> 7\. Under Global Preferences and iOS Certificate Wizard, App ID and name should probably not be required, as I think Global Preferences are to be shared between other CodenameOne application.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+
+### **Shai Almog** — April 14, 2016 at 2:36 am ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22585))
+
+> Thanks!
+> Those are great issues/RFE’s.
+> The right place to file them so they don’t get lost under our workload is the issue tracker at [http://github.com/codenameo…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+
+### **Shai Almog** — April 14, 2016 at 8:08 am ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22766))
+
+> Shai Almog says:
+>
+> Looking a bit further on this:
+>
+> 4\. Are you sure about this? It just adds the device to the provisioning profile.
+> 6\. It should have the existing app id from your app which must match the package name of your project.
+> 7\. The global version of the wizard should allow you to customize the app id as it can be a * certificate but it can reside anywhere e.g. I can make a com.mycompany.* or just plain * as my default. This matters to the provisioning profile.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+
+### **Shai Almog** — April 14, 2016 at 8:14 am ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22774))
+
+> Shai Almog says:
+>
+> How do you set the run configuration?
+> I see the problem but I can’t reproduce it to make sure the fix is correct.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+
+### **Eric Coolman** — April 28, 2016 at 9:50 pm ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22466))
+
+> Eric Coolman says:
+>
+> Great work, and thanks! 🙂
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+
+### **James van Kessel** — May 20, 2016 at 3:56 pm ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22606))
+
+> James van Kessel says:
+>
+> Hi Shai, For someone with an existing project, are there any warnings or cautions you’d give someone still using an older CN1 plugin (i am still on 3.1) before clicking “update Plugin”?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+
+### **Shai Almog** — May 21, 2016 at 3:51 am ([permalink](https://www.codenameone.com/blog/a-new-idea.html#comment-22611))
+
+> Shai Almog says:
+>
+> If you update to the latest it will be the new plugin and there is no warning. Notice that on the plugin page at IDEA you can always download the older versions of the plugin if you need it while we fix a potential issue you might run into.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-idea.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/a-new-pipeline-for-windows-phone.md b/docs/website/content/blog/a-new-pipeline-for-windows-phone.md
new file mode 100644
index 0000000000..9744816bcf
--- /dev/null
+++ b/docs/website/content/blog/a-new-pipeline-for-windows-phone.md
@@ -0,0 +1,306 @@
+---
+title: A New Pipeline For Windows Phone
+slug: a-new-pipeline-for-windows-phone
+url: /blog/a-new-pipeline-for-windows-phone/
+original_url: https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html
+aliases:
+- /blog/a-new-pipeline-for-windows-phone.html
+date: '2014-02-02'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+The Windows Phone port is one of our most painful ports, the platforms is so fragmented, volatile and rigid its remarkably hard to extract a common porting layer that will satisfy our requirements. We’ve just updated our servers with the 3rd port we did for Windows Phone, its experimental so its off by default, to activate it just use win.newPipeline=true in your build arguments.
+
+
+
+
+As you may recall our first port targeted Windows Phone 7.5 and used XNA which Microsoft killed with Windows Phone 8.
+
+
+Our second port was
+
+
+
+silverlight based and tried to dynamically create a scene-graph structure to match the graphics we are drawing in code, this is a very “imaginative” approach and it worked for most cases but had a lot of issues worst of which was very bad graphics performance and paint artifacts that were very hard to fix.
+
+The third approach takes a very different direction, we effectively create a writeable bitmap object and draw onto a huge int array representing the screen. This means we draw everything. Our initial attempt at this tried to use silverlight for this but this performed very badly, so we ported Pisces to C# and use that to some degree to get basic graphics primitives and image blitting working on the platform. This isn’t the best approach in terms of performance but its better than what we have now and might be good enough for now. If this pans out we can always port some of the low level code to direct x using the basic API’s we already implemented.
+
+
+
+
+Please try this new port and let us know both here and in the discussion forum how it affects your application.
+
+
+
+
+
+
+This was quite a bit of work that took a lot of effort we have a pretty large pipeline of tasks ahead of us both in terms of bug fixes and pending features, so if a particular issue you filed wasn’t addressed please bare with us as we work thru the backlog. Its OK to remind us occasionally since things sometimes do fall thru the cracks.
+
+
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Anonymous** — February 4, 2014 at 8:25 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21435))
+
+> Anonymous says:
+>
+> Still some minor bugs but way better than the previous support. Keep up the good work !
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — April 28, 2014 at 10:27 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21837))
+
+> Anonymous says:
+>
+> So far my development under WP has been going well, and I’m making a game. Performance wise, it’s “playable”, maybe not the best but I am using a low-end device for debugging.
+>
+> Overall it’s a good port, but better performance through through directX would be nice! 🙂
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — April 28, 2014 at 12:28 pm ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21718))
+
+> Anonymous says:
+>
+> Unfortunately I don’t think we will be able to use DirectX. The problem is that when you use DirectX we can’t use text and we can’t use widgets (e.g. TextInput), it might be possible to achieve something like that but it seems rather difficult in comparison to other platforms.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — April 30, 2014 at 5:59 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21725))
+
+> Anonymous says:
+>
+> No worries, the latest build of the game runs smooth on low-end devices! Had to improve some GC logic etc for the game.
+>
+> It really does show how powerfull CodenameOne is, great job guys!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — July 24, 2014 at 4:28 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-22104))
+
+> Anonymous says:
+>
+> Hi!
+>
+> Is the win.newPipeline=true now on by default? I tried to build with and without it and the build seamed to generate the same .xap file.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — July 24, 2014 at 2:13 pm ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21677))
+
+> Anonymous says:
+>
+> Hi,
+>
+> yes its the default. You can set it to false to disable it.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — October 30, 2014 at 5:57 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21679))
+
+> Anonymous says:
+>
+> Hello, has further improvement/updates been made since? Also what are the current limitations to this port compared to Android/IOs?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — October 30, 2014 at 9:42 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21950))
+
+> Anonymous says:
+>
+> The port is far inferior and there was no progress to speak of. From talking to our enterprise/corporate subscribers it seems they only want Windows Phone as a checklist feature and prefer we invest our efforts on the iOS/Android fronts.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — October 30, 2014 at 11:18 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-22290))
+
+> Anonymous says:
+>
+> Thanks for the feedback. I am seeing WP gaining momentum and with Windows Phone 10, it might gain more in the near future. Its a pity CN1’s direction is currently dictated by current paying customers since your initial ethos was to create a toolchain that enables developers to write code once that would run on most major platforms. I don’t blame you Shai, but it would be so nice to see good support for at least these three major platforms, which might attract more paying customers. Some colleagues of mine are advising me to invest development in web apps which are more portable for major platforms in the long run.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — October 30, 2014 at 9:56 pm ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-22301))
+
+> Anonymous says:
+>
+> Our direction was always dictated by paying users as we repeatedly stated in the discussion forum.
+>
+> Windows phone isn’t growing anywhere and got 3 total rewrites so far because of Microsoft’s flakiness.
+>
+> Try getting a non-trivial webapp to work on the mobile version of internet explorer not to mention the browser changes made in the Android 4.x branch which have no workarounds available then talk about the “portability of the web” nonsense. Anyone who says web is portable didn’t actually use it professionally on a wide scale.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — October 31, 2014 at 6:03 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21799))
+
+> Anonymous says:
+>
+> Shai, when people look at a product offered, they usually look at the company’s first web page (at least that the case with me), which states very clearly your support for major platforms such as Android, IOs, BlackBerry and Windows Phone so forgive me for not looking at your discussion forums about how they are ‘really’ supported and at what level. It initially sounds to me you support everything quite well to some extent until I read this blog article of yours.
+>
+> Agreed, Windows Phone may not have gained much traction until now, but in the country where I live, I see more people using it than iPhones surprisingly, as it is the case in some parts of Europe. I don’t understand your comment about blaming Microsoft for your support hindrance, but it would be disingenuous to blame Microsoft if you are unable to make it work properly with your platform’s architecture.
+>
+> My friend has a company that writes web apps quite successfully for various clients, both big and small. So yes, it all depends on the nature of the app, but your comment is a little strong about people that still write web apps.
+>
+> Looking at your pricing package I would have loved to pay as a corporate or enterprise customer if you support Windows Phone quite well. Do you support BB10? I reckon your customers are using the Android port for running on that platform. Going back to the main discussion, even if I am a paying customer, I won’t have the guarantee you will support Windows Phone platform because the majority of your paying customers don’t want it as a main feature (until they decide to) and I cannot compete with the majority which is my main concern and its difficult for me to invest money into your services with this uncertainty. Thanks and all the best to you, Jan.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — October 31, 2014 at 8:47 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-21822))
+
+> Anonymous says:
+>
+> If you were a corporate customer it would be financially viable for us to take the 3-6 man months of engineering effort required to rewrite the Windows Phone port.
+>
+> We do give corporate/enterprise customers the guarantee that their priority use cases will be properly supported. Obviously the effort required for Windows Phone is much bigger than most of the other tasks we face mostly due to inherent design choices made by MS.
+>
+> Just so I’m clear about the word majority, we have a few pro customers who want Windows Phone, but no Enterprise/Corporate users who are interested in it.
+>
+> We have a couple of pro users to whom BB10 is important but they are satisfied with the option of using the APK and we try to keep the chief functionality level there working with BB10.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — December 27, 2014 at 6:32 pm ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-22249))
+
+> Anonymous says:
+>
+> Hi,
+>
+> is there any progress in developing Apps for Win 8.x Phones?
+>
+> What is about ‘packing’ a wep app core within codenameone?
+>
+> is there any help article available for signing the app?
+>
+> Thanks, Jan
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — December 28, 2014 at 4:44 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-22042))
+
+> Anonymous says:
+>
+> Hi,
+>
+> there is no need/option to sign for Windows Phone. MS signs on its own and since Windows Phone doesn’t have an OTA install option (without the store) there is no real need. Currently none of our enterprise customers have made a formal request to justify the effort on Windows Phone. We support desktop development which should work well for the more popular Windows platforms such as the Surface Pro.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — February 14, 2015 at 8:32 pm ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-24177))
+
+> Anonymous says:
+>
+> Hi Shai,
+>
+> i tried to build a very simple app and deploy it to my
+>
+> Lumia 630 phone by clicking on the *.XAP File uploaded
+>
+> on OneDrive.
+>
+> It says something like (in german):
+>
+> This enterprise app cannot be installed…
+>
+> Do i have to sign it with windows phone sdk?
+>
+> I understand your last answer in the way that
+>
+> this is not necessary / possible ?
+>
+> Thanks for an answer,
+>
+> Jan
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — February 15, 2015 at 5:21 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-22102))
+
+> Anonymous says:
+>
+> Windows Phone is the only “modern” mobile OS that doesn’t support installing apps over the air (or by click) only via cable sync or thru the store beta test process.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — February 23, 2015 at 8:52 pm ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-24196))
+
+> Anonymous says:
+>
+> Hi Shai,
+>
+> sorry, but no success:
+>
+> – I tried to install the app via SD card,
+>
+> – it does not appears in the App “Store”
+>
+> – clicking on it in WP 8 file manager doesn’t work
+>
+> – i try to build it actuallay again (24-02-2015)
+>
+> the message stays the same (… cannot be installed)
+>
+> Oliver
+>
+> P.S.
+>
+> The same code (simple list of buttons) runs even
+>
+> on my very old sony ericsson k800i as j2me app…
+>
+> any idea?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+
+### **Anonymous** — February 24, 2015 at 3:45 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline-for-windows-phone.html#comment-22168))
+
+> Anonymous says:
+>
+> See [http://www.codenameone.com/…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline-for-windows-phone.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/a-new-pipeline.md b/docs/website/content/blog/a-new-pipeline.md
new file mode 100644
index 0000000000..27e6832189
--- /dev/null
+++ b/docs/website/content/blog/a-new-pipeline.md
@@ -0,0 +1,122 @@
+---
+title: A New Pipeline
+slug: a-new-pipeline
+url: /blog/a-new-pipeline/
+original_url: https://www.codenameone.com/blog/a-new-pipeline.html
+aliases:
+- /blog/a-new-pipeline.html
+date: '2014-01-26'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+One of our enterprise developers started complaining about the performance of our Android port, which forced us to take a closer look at our rendering pipeline on Android. It seems that Google’s hardware acceleration broke pretty much all the best practices of the Android 2.x era and what we had wasn’t taking full advantage of “project butter” the codename for Google’s new Android rendering layer.
+
+
+
+
+
+
+This took a lot of effort to adapt and the effort is still ongoing but we wrote a brand new rendering layer for Android, at the moment you would need to switch it on explicitly and it will only work for Android 3.x or newer. When we will feel that its stable we will flip the default switch and make this the default for all future Android builds.
+
+
+
+
+
+
+If you want to play with it just use the build argument android.asyncPaint=true
+
+
+
+
+
+
+In this mode all paint graphics operations are added to a “task” pipeline and rendered asynchronously which allows better utilization of the device GPU for some use cases. Because of that this mode returns false from Display.areMutableImagesFast() this allows us to optimize rendering to avoid double buffering paradigms where possible (e.g. within the MapComponent).
+
+
+
+
+
+
+One of the nice unintended side effects of this rendering mode is that color reproduction on the device is more accurate in subtle ways. The rasterization process on Android devices is slightly different especially on
+[
+PenTile
+](http://en.wikipedia.org/wiki/PenTile_matrix_family)
+devices (very common on Android) and when we use this approach such devices produce a more accurate result.
+
+
+
+
+
+
+Another unexpected bonus we got as a result of this change was that Android related GPU debugging tools started working for Codename One applications. Unfortunately, they indicated extreme overdraw issues for some cases. Overdraw indicates that we paint the same pixel multiple times to draw a single form which means the UI will effectively be slower. Androids tools are really good at pointing out a problem but they are awful at pointing us to the location of the problem. So we had to extend our performance monitor tool and add a special mode to it that shows the exact graphics operations performed by every component painting itself. This can be illuminating when trying to see why a specific UI is so slow, it also provides stack traces for every graphics operation.
+
+
+
+
+
+
+To try it just open the performance monitor from the simulator and select the second tab then refresh within the form you want to inspect. You can then traverse the tree of components and see the exact rendering calls that were used to draw every single component within the hierarchy.
+
+
+
+
+
+
+Using this tool we noticed that the parent form drew itself twice for every rendering cycle, it turns out that a special case needed only for transitions was active with every repaint. This was easy to fix in the core and should provide a small performance boost to Codename One applications on all platforms.
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Anonymous** — January 28, 2014 at 5:22 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline.html#comment-21912))
+
+> Anonymous says:
+>
+> Yes! Great Job!
+>
+> This is something I’ve really been waiting for.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline.html)
+
+
+### **Anonymous** — February 5, 2014 at 5:46 am ([permalink](https://www.codenameone.com/blog/a-new-pipeline.html#comment-24236))
+
+> Anonymous says:
+>
+> It’s funny because just yesterday I thought I noticed my app was running smoother, sometimes its easy to forget you eager beavers are improving our lives secretly and remotely, I appreciate this so much. As for ever draw, Im assuming you can detect if something wont be scene and not render it, a little like a 2d game tile engine. I THINK though that this new build hint is making the native browsers I have embedded in my app flash.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline.html)
+
+
+### **Anonymous** — March 12, 2014 at 1:13 pm ([permalink](https://www.codenameone.com/blog/a-new-pipeline.html#comment-21796))
+
+> Anonymous says:
+>
+> Excellent! I think the android port has been in need of a serious performance review for quite some time now. Android being the mobile platform with the most market share, it would be quite expedient if performance on this platform is focused on more intently. Looking forward to hearing more about this. Thanks!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fa-new-pipeline.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/a-thank-you-an-important-update-on-android-builds.md b/docs/website/content/blog/a-thank-you-an-important-update-on-android-builds.md
new file mode 100644
index 0000000000..a4fea59ca9
--- /dev/null
+++ b/docs/website/content/blog/a-thank-you-an-important-update-on-android-builds.md
@@ -0,0 +1,94 @@
+---
+title: A Thank You & an Important Update On Android Builds
+slug: a-thank-you-an-important-update-on-android-builds
+url: /blog/a-thank-you-an-important-update-on-android-builds/
+original_url: https://www.codenameone.com/blog/a-thank-you-an-important-update-on-android-builds.html
+aliases:
+- /blog/a-thank-you-an-important-update-on-android-builds.html
+date: '2016-02-02'
+author: Shai Almog
+---
+
+
+
+We’d like to thank all of you who signed up to the pro subscription, the release of 3.3 is the perfect time to
+do that. So we are opening up the JavaScript build target for 1 year until March 1st 2017 to all current pro subscribers!
+If you have a pro subscription you can start sending a JavaScript build right away and experiment with porting
+your app to the web…
+If you don’t have a pro license currently then you have until March 15th to upgrade and enjoy this offer. After
+March 15th the JavaScript port will return to enterprise only status for everyone who didn’t signup prior to that.
+
+If you are not familiar with the JavaScript port check out Steve’s [great writeup](/blog/javascript-port.html)
+on it, you can also try out some of our demos live right now [here](/demos.html)
+(using desktop or mobile browsers). Just click the JS Port link at the bottom right section of a demo e.g.
+restaurant or property cross.
+FYI If you cancel the subscription during this time or let it lapse this capability will be lost so make sure to keep it in place.
+
+#### Memory Issue on Android Builds
+
+With the switch to gradle in Android builds we experienced memory issues for some cases when building huge
+apps. For some cases ading `android.gradle=false` to the build hints was enough but for others
+not so much.
+
+The problem relates to the size of Google Play Services which are an essential part of Android applications but
+have grown to a size that is pretty big. We need play services for better location tracking, in-app-purchase, push
+notification, maps etc.
+In the past we had the build hint `android.includeGPlayServices` which
+tried to be smart about play services but its a bit too coarse as it only accepts true/false.
+
+To alleviate this issue we deprecated the `android.includeGPlayServices` and are introducing
+the new build hints below that will allow you to selectively include a play service. This means that future builds
+to the Codename One build servers can have one of the following 5 states:
+
+ 1. `android.includeGPlayServices=true` & one or more of the `android.playService`
+entries defined – this is an illegal state and will cause the build to fail
+ 2. `android.includeGPlayServices=true` & no `android.playService`
+entries defined – this will fallback to compatibility mode
+ 3. `android.includeGPlayServices=false` & no `android.playService`
+entries defined – play services won’t be included
+ 4. `android.includeGPlayServices` undefined & no `android.playService`
+entries defined – some play services will be included by default specifically: plus, auth, base, analytics, gcm, location, maps,
+ads.
+ 5. `android.includeGPlayServices` undefined & one or more `android.playService`
+entries defined – only the play services you explicitly select will be included.
+
+The last two are a bit confusing so just to clarify if you do this:
+
+
+ android.playService.plus=true
+
+The only play service included will be plus. However, if you don’t define any build hints specifically the above list
+of play services will be included. This is a “sensible default mode” that we picked to make the transition easier.
+
+The list of supported hints follows, they all accept true/false as arguments for inclusion/exclusion.
+
+
+ android.playService.plus
+ android.playService.auth
+ android.playService.base
+ android.playService.identity
+ android.playService.indexing
+ android.playService.appInvite
+ android.playService.analytics
+ android.playService.cast
+ android.playService.gcm
+ android.playService.drive
+ android.playService.fitness
+ android.playService.location
+ android.playService.maps
+ android.playService.ads
+ android.playService.vision
+ android.playService.nearby
+ android.playService.panorama
+ android.playService.games
+ android.playService.safetynet
+ android.playService.wallet
+ android.playService.wearable
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/accelerometer-code-freeze.md b/docs/website/content/blog/accelerometer-code-freeze.md
new file mode 100644
index 0000000000..d026f3a95b
--- /dev/null
+++ b/docs/website/content/blog/accelerometer-code-freeze.md
@@ -0,0 +1,43 @@
+---
+title: Accelerometer & Code Freeze
+slug: accelerometer-code-freeze
+url: /blog/accelerometer-code-freeze/
+original_url: https://www.codenameone.com/blog/accelerometer-code-freeze.html
+aliases:
+- /blog/accelerometer-code-freeze.html
+date: '2015-04-12'
+author: Shai Almog
+---
+
+
+
+Devices have sensors such as accelerometer, GPS and up until now our support for them was relatively basic.
+Chen recently introduced a [cn1lib](https://github.com/chen-fishbein/sensors-codenameone)
+that includes support for various types of sensors on the device. Its really simple to use:
+
+
+ SensorsManager sensor = SensorsManager.getSenorsManager(SensorsManager.TYPE_ACCELEROMETER);
+ if (sensor != null) {
+ sensor.registerListener(new SensorListener() {
+ public void onSensorChanged(long timeStamp, float x, float y, float z) {
+ //do your stuff here...
+ }
+ });
+ }
+
+Check it out if you need access to such features.
+
+### Code Freeze
+
+We will be entering code freeze later today which should allow us to gear up towards the 3.0 release of Codename One
+in two weeks. All commits will be made against an issue and always with a peer review. Releasing 3.0 will allow
+us to improve versioned builds and cleanup our issue tracker. We already cleared more than 100 issues in the past
+week in an effort to make this a high quality polished release.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/accordion-component.md b/docs/website/content/blog/accordion-component.md
new file mode 100644
index 0000000000..2f66dd8d58
--- /dev/null
+++ b/docs/website/content/blog/accordion-component.md
@@ -0,0 +1,52 @@
+---
+title: Accordion Component
+slug: accordion-component
+url: /blog/accordion-component/
+original_url: https://www.codenameone.com/blog/accordion-component.html
+aliases:
+- /blog/accordion-component.html
+date: '2016-06-18'
+author: Chen Fishbein
+---
+
+
+
+The [Accordion](https://www.codenameone.com/javadoc/com/codename1/components/Accordion.html) ui pattern
+is a vertically stacked list of items. Each item can be opened/closed to reveal more content similarly to a
+[Tree](https://www.codenameone.com/javadoc/com/codename1/ui/tree/Tree.html) however unlike the
+`Tree` the `Accordion` is designed to include containers or arbitrary components rather than model based data.
+
+This makes the `Accordion` more convenient as a tool for folding/collapsing UI elements known in advance
+whereas a `Tree` makes more sense as a tool to map data e.g. filesystem structure, XML hierarchy etc.
+
+Note that the `Accordion` like many composite components in Codename One is scrollable by default which
+means you should use it within a non-scrollable hierarchy. If you wish to add it into a scrollable `Container` you
+should disable it’s default scrollability using `setScrollable(false)`.
+
+
+ Form f = new Form("Accordion", new BorderLayout());
+ Accordion accr = new Accordion();
+ accr.addContent("Item1", new SpanLabel("The quick brown fox jumps over the lazy dogn"
+ + "The quick brown fox jumps over the lazy dog"));
+ accr.addContent("Item2", new SpanLabel("The quick brown fox jumps over the lazy dogn"
+ + "The quick brown fox jumps over the lazy dogn "
+ + "The quick brown fox jumps over the lazy dogn "
+ + "The quick brown fox jumps over the lazy dogn "
+ + ""));
+
+ accr.addContent("Item3", BoxLayout.encloseY(new Label("Label"), new TextField(), new Button("Button"), new CheckBox("CheckBox")));
+
+ f.add(BorderLayout.CENTER, accr);
+ f.show();
+
+
+
+Figure 1. Accordion component
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/accordion-control-xcode-migration-update.md b/docs/website/content/blog/accordion-control-xcode-migration-update.md
new file mode 100644
index 0000000000..2044efa974
--- /dev/null
+++ b/docs/website/content/blog/accordion-control-xcode-migration-update.md
@@ -0,0 +1,55 @@
+---
+title: Accordion Control & Xcode Migration Update
+slug: accordion-control-xcode-migration-update
+url: /blog/accordion-control-xcode-migration-update/
+original_url: https://www.codenameone.com/blog/accordion-control-xcode-migration-update.html
+aliases:
+- /blog/accordion-control-xcode-migration-update.html
+date: '2016-08-31'
+author: Shai Almog
+---
+
+
+
+In the coming update we have a new API to expand/collapse an `Accordion` component programmatically similar to the `Tree` component.
+
+To achieve this we introduced three new API’s to the `Accordion` class:
+
+
+ /**
+ * Returns the body component of the currently expanded accordion element or null if none is expanded
+ * @return a component
+ */
+ public Component getCurrentlyExpanded();
+
+ /**
+ * Expands the accordion with the given "body"
+ * @param body the body component of the accordion to expand
+ */
+ public void expand(Component body);
+
+ /**
+ * Closes the accordion with the given "body"
+ * @param body the body component of the accordion to close
+ */
+ public void collapse(Component body);
+
+All of these methods get/return the body of the accordion which is probably the best way to identify an accordion node.
+
+### Xcode Migration
+
+The migration to the new xcode servers is going well so far. We did experience some minor issues the biggest was the need to use `https` has kicked in as a result of the newer tooling.
+
+Pretty much everything seems to be in order and we migrated other servers to pick up the load so builds should be back to their typical build times. Before that builds might have been slower but once the migration is complete we’ll have more servers than we had when we started off making builds even faster.
+
+Right now we still have the `iphone_old` build target running, we plan to remove it by 3.6 (due December) so if you depend on it we suggest you keep us posted!
+
+Notice that once it’s removed we won’t be able to restore it as it depends on a legacy version of Mac OS X no longer sold.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/adding-google-play-ads.md b/docs/website/content/blog/adding-google-play-ads.md
new file mode 100644
index 0000000000..a8e5437bd9
--- /dev/null
+++ b/docs/website/content/blog/adding-google-play-ads.md
@@ -0,0 +1,172 @@
+---
+title: Adding Google Play Ad's
+slug: adding-google-play-ads
+url: /blog/adding-google-play-ads/
+original_url: https://www.codenameone.com/blog/adding-google-play-ads.html
+aliases:
+- /blog/adding-google-play-ads.html
+date: '2013-12-03'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+We are officially code frozen so only critical bug fixes should be resolved until the actual release. The very last feature to make it in is support for
+[
+Google Play Ads
+](https://developers.google.com/mobile-ads-sdk/)
+on iOS/Android. We currently only work with the Admob SDK as we move along we might add additional options.
+
+
+To enable mobile ads just
+[
+create an ad unit
+](https://apps.admob.com/?pli=1#monetize/adunit:create)
+in Admob’s website, you should end up with the key similar to this:
+
+ca-app-pub-8610616152754010/3413603324
+
+
+To enable this for Android just define
+
+android.googleAdUnitId=
+
+
+ca-app-pub-8610616152754010/3413603324 in the build arguments and for iOS use the same as in ios.googleAdUnitId. The rest is seamless, the right ad will be created for you at the bottom of the screen and the form should automatically shrink to fit the ad. This shrinking is implemented differently between iOS and Android due to some constraints but the result should be similar and this should work reasonably well with device rotation as well.
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **juanpgarciac** — May 22, 2015 at 9:17 pm ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22216))
+
+> juanpgarciac says:
+>
+> There is a way to check the functionablity in the debug process ? cause I’d follow the steps and it doesn’t show anything… thanks for sharing
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Shai Almog** — May 23, 2015 at 6:32 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22112))
+
+> Shai Almog says:
+>
+> Its supposed to be seamless. On which device are you running into a problem?
+> Notice this will only work on devices and not the simulator.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **App Maker** — January 10, 2016 at 9:30 pm ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22406))
+
+> App Maker says:
+>
+> monetize option is not available! can u please tell me why? n how can i get it?!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Shai Almog** — January 11, 2016 at 3:22 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22498))
+
+> Shai Almog says:
+>
+> We removed it as it was hard to maintain across IDE’s. We provided several newer monetization options including several ad based cn1libs [https://www.codenameone.com…]()
+>
+> And this option which doesn’t require the monetization section, just a build hint.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Pugazhendi E** — March 26, 2016 at 7:36 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22668))
+
+> Pugazhendi E says:
+>
+> admob ads are not coming…I set the admob adunit ID in build arguments in netbeans ide…if i run a app, app ll work bt ads ll nt come…I followed the codename procedure… help me..
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Shai Almog** — March 27, 2016 at 4:25 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22459))
+
+> Shai Almog says:
+>
+> Is this on the device? Which device type?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Jean Carlos Rojas Ramirez** — June 8, 2016 at 11:35 pm ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22833))
+
+> Jean Carlos Rojas Ramirez says:
+>
+> I am working on an app and I can add the hint for Android but I would like to know which I should use for windows phone. Thanks,
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Shai Almog** — June 9, 2016 at 5:05 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-21629))
+
+> Shai Almog says:
+>
+> We don’t currently support Windows Phone with ads. AFAIK Googles AdMob isn’t available on Windows Phone.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Jean Carlos Rojas Ramirez** — June 9, 2016 at 5:24 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22826))
+
+> Jean Carlos Rojas Ramirez says:
+>
+> Right now there is a version for windows phone for AdMob. If we build a windows phone app we can add the AdMob AdUnit for those builds.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Shai Almog** — June 10, 2016 at 3:46 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22612))
+
+> Shai Almog says:
+>
+> OK. Either way Windows Phone is on the way out at Microsoft and I doubt Google would support that version. We are switching to the UWP port for universal Windows 10 support.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Pugazhendi E** — July 25, 2016 at 1:27 pm ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22843))
+
+> Pugazhendi E says:
+>
+> yes sir..I ve tested this app with lenevo smartphone….app works well but ads ll not come
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+
+### **Shai Almog** — July 26, 2016 at 4:11 am ([permalink](https://www.codenameone.com/blog/adding-google-play-ads.html#comment-22928))
+
+> Shai Almog says:
+>
+> That’s probably related to these changes [https://github.com/codename…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fadding-google-play-ads.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/admob-interstitial-ads-supertabs.md b/docs/website/content/blog/admob-interstitial-ads-supertabs.md
new file mode 100644
index 0000000000..f2598ad857
--- /dev/null
+++ b/docs/website/content/blog/admob-interstitial-ads-supertabs.md
@@ -0,0 +1,56 @@
+---
+title: Admob Interstitial Ads & Supertabs
+slug: admob-interstitial-ads-supertabs
+url: /blog/admob-interstitial-ads-supertabs/
+original_url: https://www.codenameone.com/blog/admob-interstitial-ads-supertabs.html
+aliases:
+- /blog/admob-interstitial-ads-supertabs.html
+date: '2014-10-26'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Ram (the developer of
+[
+yhomework
+](http://www.codenameone.com/featured-yhomework.html)
+), wanted to improve his ad revenue on Android/iOS thru interstitial (full screen) ads and integrated those using native interfaces. Kindly enough he contributed these changes back and Chen packaged this as a cn1lib which you can now easily use to add support for
+[
+full screen ads to your Android/iOS apps
+](https://code.google.com/p/admobfullscreen-codenameone/)
+.
+
+On a different subject a long time RFE has been the ability to customize the tabs using a more powerful API. The main blocker for that has been the hardcoding of Buttons (or really radio buttons) as tabs in the Tabs component.
+
+With the upcoming version of Codename One you will be able to replace the usage of Button with just about anything by overriding a set of protected methods in the Tabs component. These effectively allow the Tabs class to understand the structure of your potentially complex class and communicate the requirements of the component. In the relatively simplistic implementation below I just used a layered layout to place a close button in the right side of every tab. This is code I used in the kitchen sink demo to test this effect.
+
+
+
+
+
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/aligning-prices.md b/docs/website/content/blog/aligning-prices.md
new file mode 100644
index 0000000000..ee809af488
--- /dev/null
+++ b/docs/website/content/blog/aligning-prices.md
@@ -0,0 +1,258 @@
+---
+title: Aligning Prices
+slug: aligning-prices
+url: /blog/aligning-prices/
+original_url: https://www.codenameone.com/blog/aligning-prices.html
+aliases:
+- /blog/aligning-prices.html
+date: '2015-05-10'
+author: Shai Almog
+---
+
+
+
+Our pricing has been inconsistent with the rest of the industry for quite some time specifically the price of the
+basic subscription which is a losing tier. Based on Industry norms the basic subscription should be far more
+expensive and doesn’t come close to covering the costs of running Codename One’s extensive cloud
+infrastructure. So on June 1st we will raise the price of the basic subscription to 19USD which is still very
+affordable. Notice that **if you are a current subscriber or sign up before June 1st you can keep paying
+at the 9USD rate!**
+However, if you let your subscription lapse we will not be able to recover it and you would need to switch to the 19USD
+level…
+
+This is an important step for the health of the company as the costs of hosting our infrastructure are growing fast
+and basic subscriptions just aren’t carrying the weight.
+
+As part of this move we also analyzed our build server usage and noticed many developers in the free tier who
+place a very heavy toll on the build servers with long builds that eventually cost in service degradation to everyone.
+That is one of the reasons we originally made the iOS build quota so limiting…
+
+So we are now reducing the credits for iOS builds to 8 credits per build (from 20) but also placing a size quotas on
+builds from free users to reduce both our storage costs and build times (upload/download and server instance costs).
+We are sure this step will improve performance for everyone involved and help people try Codename One before
+committing to a paid account.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Maaike Z** — May 12, 2015 at 7:43 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-21649))
+
+> Maaike Z says:
+>
+> I understand the price change, but I’m not happy with it (I do this as a hobby). For a company this is ‘nothing’ of course. But why don’t you make offline building easier? Then you don’t need the expensive infrastructure (or at least users place less toll on the build servers).
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Shai Almog** — May 13, 2015 at 4:27 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22250))
+
+> Shai Almog says:
+>
+> That’s why we increased the free quota for iOS builds to make it easier on the hobbyist crowd to work without a subscription at all.
+> Some people seem to think we have an ability to build offline, we send builds to the build servers just like everyone else… Offline building is technically impractical since the build process is so complex, fluid and has so many dependencies.
+> And don’t get me started on manpower costs… Offering things for free doesn’t keep the lights on in any company.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Fabrício Cabeça** — May 13, 2015 at 7:39 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22383))
+
+> Fabrício Cabeça says:
+>
+> Hi Maaike, I think you may be understimating the offline building
+> costs, and you should add to it your own time to keep your offline build
+> server(s) up to date with all SDKs. I have a pro account and I have the
+> knowledge and a minimal infrastructure that could be used to create
+> offline build servers, but I prefer to focus in the development itself, that’s what codenameone is all about.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Maaike Z** — May 15, 2015 at 1:52 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22110))
+
+> Maaike Z says:
+>
+> @codenameone:disqus : I totally understand that you can’t offer everything for free. It’s impossible for a company to do everything for free. That doesn’t wipe away my wish to have a good product for less :).
+>
+> @fabriciocabeca:disqus I wish I could choose. I have time to make a build server and if everything is set up well once, I only have to keep everything up to date. Sometimes I prefer to have my own server to be not dependent of Codename One (that’s actually one of the drawbacks for me, the dependence of the company).
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **J.C** — May 15, 2015 at 3:09 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-24169))
+
+> J.C says:
+>
+> So, just to clarify, if I pay 9USD before end of this month, how much will I pay at end of June? 19USD or still 9USD? Also, why don’t you also include an option for annual or 6 month subscriptions for Basic users to help us save cost for monthly transactions fees?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Shai Almog** — May 15, 2015 at 4:26 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22138))
+
+> Shai Almog says:
+>
+> Yes.
+> We didn’t provide annual subscriptions because they won’t save much on the basic level (19 is still pretty low) you do have a point though, its something we should add.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **J.C** — May 15, 2015 at 5:49 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-21650))
+
+> J.C says:
+>
+> Sorry you didn’t answer my question, 19USD or 9USD?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Chen Fishbein** — May 16, 2015 at 6:07 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22251))
+
+> Chen Fishbein says:
+>
+> if you are a current subscriber or sign up before June 1st you can keep paying at the 9USD
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Maaike Z** — May 21, 2015 at 9:15 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22073))
+
+> Maaike Z says:
+>
+> What about the 1mb quota for not subscribed users? Is it de jar sent to the server with a max quota of 1 mb or the final app or … ?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Shai Almog** — May 22, 2015 at 3:44 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22411))
+
+> Shai Almog says:
+>
+> That email was poorly worded IMO. It was sent without my review.
+> The limit applies to the built JAR before sending otherwise it would be meaningless since the servers would have had to do all the work (and wasted the paid quotas we are spending) so there would have been no saving.
+> To be clear we didn’t just pull out that number, we reviewed the sizes of free user builds coming in and concluded that well over 90% would fit under this limit right now or with very small modifications. The goal is to stop the outliers so we can provide essentially more quota (e.g. increased iOS build credits). Things like the kitchen sink demo which is really wasteful (several huge themes, a video etc.) will not fit, but its not a common app.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Eric** — May 22, 2015 at 9:31 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22111))
+
+> Eric says:
+>
+> The resource file can only have the theme which can be bigger than 1Mb. This resource file is always added to the jar file before sending so the problem persist because a very simple app with a custom theme will not fit too because of the size of the theme file. How can you resolve that?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Shai Almog** — May 22, 2015 at 10:14 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-24170))
+
+> Shai Almog says:
+>
+> You can dynamically download a theme on the device to workaround that but yest that is something that won’t work as well…
+> Most themes should be optimizable see: [http://codenameone.com/blog…]()
+>
+> Unfortunately more elaborate themes won’t make it in. The main problem is that we can’t really tell how large your code is and how large the theme is (before doing all the work) and even if we could it would still cost us quite a bit more to build/host larger files. We tried to draw a line that makes sense for most and would allow us the leverage to increase build quotas which we felt were lacking.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **huwab0** — May 22, 2015 at 10:15 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22355))
+
+> huwab0 says:
+>
+> This effectively kills my project.
+>
+> Our project is much larger than 1MB because we suppy a chinese font and several word and character lists.
+>
+> I rarely build on the server, so I wouldn’t mind if big jars cost more build credits!?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Eric** — May 22, 2015 at 10:32 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22296))
+
+> Eric says:
+>
+> Shai, you must make a poll to users to know if they want more ios buid quotas or more app size. It’s not good to decide for users. If you cannot know the size of the code in the jar file so your first answer is not the right answer. You can not limit us if you cannot just track the size of our code without the resource file. This a serious problem that you create for developers
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Chen Fishbein** — May 22, 2015 at 10:56 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22139))
+
+> Chen Fishbein says:
+>
+> Resource files when packaged to the jar are reduced by size, besides plenty of complex themes are in the 400-500k range uncompressed.
+> This should be sufficient for a user to properly evaluate the product before he/she commits to a paid account.
+> This is a crucial step for the health of the company we need the heavy users to start and help us pay the bills $9 or even $19 is pretty valuable for the product and service we provide.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Chen Fishbein** — May 22, 2015 at 10:58 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22363))
+
+> Chen Fishbein says:
+>
+> I hope paying $9 or even $19 for our service shouldn’t be the mortal point to your project
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **huwab0** — May 22, 2015 at 1:00 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-24179))
+
+> huwab0 says:
+>
+> Actually, we try to make our project break even, but since there is hardly any money in our kind of applications, actually all costs need to be avoided.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **rhg1968** — May 22, 2015 at 1:04 pm ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22418))
+
+> rhg1968 says:
+>
+> I for one totally support this change and understand why it is necessary. Codename one has been more than generous allowing you to build production applications for free for years. A company can’t go on this way forever and afford to stay in business and improve the system. I think it’s great that the free tier will still give people a lot so that they can truly evaluate the platform. I also think it’s more than generous to offer us the 9 price before raising the price. I have subscribed to the basic level now and look forward to developing with Codename one.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Benjamin Vander Stichelen** — May 23, 2015 at 2:35 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22338))
+
+> Benjamin Vander Stichelen says:
+>
+> This will kill the project i’m working on, when creating the app every 2-3 updates creates a bug and time to investigate what went wrong.
+> I understand that every company needs adjustments. Buth why don’t do it like google.
+> The existing accounts don’t get changes the new accounts get limitations.
+>
+> When google decides to remove the project it’s removed buth meanwhile it’s on you got it like it was from the beginning for you.
+>
+> I’m surious to need to say that when they ask me would you do it again with codenameone the answer is no.
+>
+> That’s my opinion, now you are forcing us to do it from now on with the existing proyect.
+>
+> Greeting,s
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+
+### **Chen Fishbein** — May 23, 2015 at 5:37 am ([permalink](https://www.codenameone.com/blog/aligning-prices.html#comment-22209))
+
+> Chen Fishbein says:
+>
+> I understand the frustration, but in order to keep on providing the product and service in high quality the heavy accounts needs to start and pay.
+> I hope we do become google one day, but even google when they change their terms or cancel projects it will effect all their users.
+> We might consider exceptions in special cases, feel free to reach out to me.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faligning-prices.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/alphabet-scroll.md b/docs/website/content/blog/alphabet-scroll.md
new file mode 100644
index 0000000000..85ec7803d3
--- /dev/null
+++ b/docs/website/content/blog/alphabet-scroll.md
@@ -0,0 +1,161 @@
+---
+title: Alphabet Scroll
+slug: alphabet-scroll
+url: /blog/alphabet-scroll/
+original_url: https://www.codenameone.com/blog/alphabet-scroll.html
+aliases:
+- /blog/alphabet-scroll.html
+date: '2016-07-17'
+author: Shai Almog
+---
+
+
+
+We got a lot of requests from developers over the years to do an iOS style alphabet side scroll. Some developers
+implements such scrolling but no one made it generic or contributed it back. So a recent stack overflow question
+got me thinking about how easy it would be to actually implement something like that and I decided to try…
+
+I ended up building this in 10 minutes and the concept is remarkably simple. I have two containers, one contains
+the list of the people and the other one contains the letters used for these people. Notice that I chose to only use
+letters that used in the names, I could have just hardcoded the English alphabet but chose to avoid that as this
+would break for internationalization and include letters that might not be common in such cases such as ‘Z’.
+
+Thanks to the usage of layered layout the containers just appear on top of one another, notice that in the sample
+code below I had to cancel the default scrollability of the form to allow the two containers to have their own
+scrollability. The scrolling isn’t nested in this case since these are two separate containers that just happen
+to be one on top of the other.
+
+We scroll to the selected component by finding it as we loop over the components in the actual container and
+using `scrollComponentToVisible` which is pretty convenient for this case.
+
+**Check out the live demo on the right side thanks to the JavaScript port**
+
+You can also check out the full project on github [here](https://github.com/codenameone/AlphabetScroll) and see
+the relevant code below:
+
+
+ String[] characters = { "Tyrion Lannister", "Jaime Lannister", "Cersei Lannister", "Daenerys Targaryen",
+ "Jon Snow", "Petyr Baelish", "Jorah Mormont", "Sansa Stark", "Arya Stark", "Theon Greyjoy",
+ "Bran Stark", "Sandor Clegane", "Joffrey Baratheon", "Catelyn Stark", "Robb Stark", "Ned Stark",
+ "Robert Baratheon", "Viserys Targaryen", "Varys", "Samwell Tarly", "Bronn","Tywin Lannister",
+ "Shae", "Jeor Mormont","Gendry","Tommen Baratheon","Jaqen H'ghar","Khal Drogo","Davos Seaworth",
+ "Melisandre","Margaery Tyrell","Stannis Baratheon","Ygritte","Talisa Stark","Brienne of Tarth","Gilly",
+ "Roose Bolton","Tormund Giantsbane","Ramsay Bolton","Daario Naharis","Missandei","Ellaria Sand",
+ "The High Sparrow","Grand Maester Pycelle","Loras Tyrell","Hodor","Gregor Clegane","Meryn Trant",
+ "Alliser Thorne","Othell Yarwyck","Kevan Lannister","Lancel Lannister","Myrcella Baratheon",
+ "Rickon Stark","Osha","Janos Slynt","Barristan Selmy","Maester Aemon","Grenn","Hot Pie",
+ "Pypar","Rast","Ros","Rodrik Cassel","Maester Luwin","Irri","Doreah","Eddison Tollett","Podrick Payne",
+ "Yara Greyjoy","Selyse Baratheon","Olenna Tyrell","Qyburn","Grey Worm","Meera Reed","Shireen Baratheon",
+ "Jojen Reed","Mace Tyrell","Olly","The Waif","Bowen Marsh"
+ };
+
+ Toolbar.setGlobalToolbar(true);
+
+ Form f = new Form("Letter Scroll", new LayeredLayout());
+
+ f.setScrollable(false);
+ Container characterContainer = new Container(BoxLayout.y());
+ Container lettersContainer = new Container(BoxLayout.y());
+ characterContainer.setScrollableY(true);
+ lettersContainer.setScrollableY(true);
+
+ char lastLetter = 0;
+ Arrays.sort(characters, new CaseInsensitiveOrder());
+ for(String character : characters) {
+ MultiButton mb = new MultiButton(character);
+ characterContainer.add(mb);
+ char c = Character.toUpperCase(character.charAt(0));
+ if(c != lastLetter) {
+ lastLetter = c;
+ Button btn = new Button("" + lastLetter);
+ lettersContainer.add(btn);
+ btn.getAllStyles().setPadding(0, 0, 0, 0);
+ btn.getAllStyles().setMargin(0, 0, 0, 0);
+ btn.addActionListener(e -> {
+ for(Component cmp : characterContainer) {
+ MultiButton m = (MultiButton)cmp;
+ if(Character.toUpperCase(m.getTextLine1().charAt(0)) == c) {
+ characterContainer.scrollComponentToVisible(m);
+ return;
+ }
+ }
+ });
+ }
+ }
+
+ f.add(characterContainer).
+ add(BorderLayout.east(lettersContainer));
+
+ f.show();
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Chidiebere Okwudire** — July 19, 2016 at 7:39 am ([permalink](https://www.codenameone.com/blog/alphabet-scroll.html#comment-22861))
+
+> Chidiebere Okwudire says:
+>
+> Nice and short! Would it be possible to animate the alphabet list as happens on some phones. So pressing down on the list and scrolling causes the list to kind of bump out around the position?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Falphabet-scroll.html)
+
+
+### **Ross Taylor** — July 19, 2016 at 8:56 am ([permalink](https://www.codenameone.com/blog/alphabet-scroll.html#comment-22644))
+
+> Ross Taylor says:
+>
+> Neat. However when I scroll the list, the title bar disappears and is turned into a blank space. Is this suppose to happen?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Falphabet-scroll.html)
+
+
+### **Chidiebere Okwudire** — July 19, 2016 at 1:29 pm ([permalink](https://www.codenameone.com/blog/alphabet-scroll.html#comment-22818))
+
+> Chidiebere Okwudire says:
+>
+> One more thing: Is there a catalog of these handy features? Something as simple as an appendix in the user manual, for example, that refers to the corresponding blog posts. I don’t know about others but it happens quite often that I want to do something and I remember once reading about it but I’m not sure where and how to find the post quickly. It would be nice if there’s an overview and also make these handy utilties more visible to newcomers.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Falphabet-scroll.html)
+
+
+### **Shai Almog** — July 20, 2016 at 4:20 am ([permalink](https://www.codenameone.com/blog/alphabet-scroll.html#comment-22580))
+
+> Shai Almog says:
+>
+> You can just set the UIID of one of them to selected or even just increase the font or padding so this would be easy and should “just work”. The main challenge is detecting where you are in the scroll. When a user scrolls by pressing a button this should be pretty easy but when the user scrolls via touch this might be more challenging. You can use the scroll listener which should be pretty easy to work with and then hack something with getComponentAt(x, y) to find the location you are currently in.
+>
+> Another approach which I haven’t tested but might be more elegant is this:
+> After constructing and initializing the layout loop over the component and assign a Y range to every alphabet letter. getY() of a specific component should return the right scroll offset and this should work nicely with the scroll listener.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Falphabet-scroll.html)
+
+
+### **Shai Almog** — July 20, 2016 at 4:20 am ([permalink](https://www.codenameone.com/blog/alphabet-scroll.html#comment-22733))
+
+> Shai Almog says:
+>
+> That’s a bug in the JavaScript port, Steve just committed a fix for this.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Falphabet-scroll.html)
+
+
+### **Shai Almog** — July 20, 2016 at 4:23 am ([permalink](https://www.codenameone.com/blog/alphabet-scroll.html#comment-22977))
+
+> Shai Almog says:
+>
+> The problem with that is that someone needs to maintain it and if something doesn’t make it there then people assume it doesn’t exist. Sometimes it’s better off to have nothing than to have something that’s half done.
+>
+> The thing I’d really like to add to the developer guide is a big section on cn1libs covering usage of the top cn1libs e.g. parse, maps, bouncy castle etc. but I can’t seem to find the time/person to do that.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Falphabet-scroll.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/alternative-app-stores.md b/docs/website/content/blog/alternative-app-stores.md
new file mode 100644
index 0000000000..805cc4de3d
--- /dev/null
+++ b/docs/website/content/blog/alternative-app-stores.md
@@ -0,0 +1,64 @@
+---
+title: Alternative App Stores
+slug: alternative-app-stores
+url: /blog/alternative-app-stores/
+original_url: https://www.codenameone.com/blog/alternative-app-stores.html
+aliases:
+- /blog/alternative-app-stores.html
+date: '2015-06-15'
+author: Shai Almog
+---
+
+
+
+Let’s face it, your app is probably a commodity. As noted by [Wikipedia](http://en.wikipedia.org/wiki/Commodity),
+“a commodity has full or partial fungibility;
+that is, the market treats its instances as equivalent or nearly so with no regard to who produced them.” In basic English
+it means that your product can be easily replaced in part or completely by another to satisfy the needs of the market.
+For 99% of apps out there this means that if a user doesn’t find your app, they’ll pick another one that they think fills
+the need they’re looking to satisfy. This the same whether your app is a game, a productivity app or any other category.
+
+As the app ecosystem has evolved the challenge of reaching consumers for app developers has grown more and more difficult.
+From a technical perspective the promise of reduced fragmentation hasn’t panned out even with so many OSs
+already sitting in the graveyard and others staring the grim reaper straight in the eyes. Any app discovery gains
+won as a result of consumers migrating to two primary OSs have been swamped by the sheer volume of apps
+developed and now available in the main app stores. Imagine walking into Walmart and there being 1 million items stocked on the shelves. How would anyone find your product?
+
+#### Why the Walmart strategy fails
+
+In the world of physical products, unless a manufacturer has an established brand or a ton of resources, they don’t
+launch their product in Walmart yet every publisher rushes to get their app into iTunes and Google Play. Since
+getting on the shelf is the easy part, it’s much more important to get people using your quality app wherever it is they
+choose to shop than it is to drive people into the iTunes or Google Play stores. Getting on their shelves is a basic requirement but far from sufficient for success. Remember, your app is a commodity – if it’s not available when and where users are shopping then it’s a missed opportunity. We often hear the question “why does anyone use anything other than Google Play?”. In reality that’s precisely the wrong question to ask. The correct question is “Do you want your app to miss out on the 100s of millions of downloads that are taking place outside of Google Play?”
+
+Even with a quality app, like any startup business, your odds of being successful aren’t very good. The One Platform Foundation’s
+[Android AppStore Market Overview](http://www.onepf.org/appstores/) noted that alternative app stores significantly improve the chances of your app being discovered by consumers. “Submitting your app to alternative appstores will increase your chances of being featured by more than 20 times.” The job of any small business owners is to give their business the best chance to be successful in a highly competitive marketplace. This has never been more important than for app publishers today.
+
+#### Don’t ignore the global audience
+
+Being everywhere really means “Think global – act local”. Do not forget about language localization and the importance of this.
+[Case studies from online gaming](http://blog.xsolla.com/2014/03/05/localization/) has shown
+that bounce rates can drop significantly and conversion and revenue drastically. For instance, 5% of the apps in Russia are localized, yet generate
+[70% of the revenue](http://blog.codengo.com/2012/07/going-local-the-importance-of-language-for-mobile-app-revenues/).
+
+Finally, alternative app stores are here to stay. Why, you might ask, with Google and Apple holding such a monopoly? App stores are a key piece of the strategic plan for companies like Amazon, Nokia, Yandex, Samsung, Baidu and the list goes on. They’re investing in app stores for the long run.
+
+#### So what should you be doing?
+
+You probably can’t ignore paid promotion at some point, but the truth here is that
+[costs are rising exponentially](http://www.gamesindustry.biz/articles/2015-05-29-mobile-marketing-costs-continue-to-rise-exponentially).
+Another way out is to make sure your app is in as many stores as possible.
+[CodeNgo helps you with that](http://www.codengo.com/features/?utm_source=codenameone&utm_medium=guestpost&utm_campaign=altappstorespost).
+For less than $1 store you’ll have shelf space in the 7’11 of app stores – or the Safeway, perhaps even
+“near the cash register” (i.e. front page). That is a huge improvement from the back of the warehouse in Google Play.
+Apple and Google aren’t going anywhere soon but their grip on the consumer will lessen over time.
+
+It’s about the consumer, not the store. Be where the consumers are.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/always-on-top-style-parser.md b/docs/website/content/blog/always-on-top-style-parser.md
new file mode 100644
index 0000000000..5e6666e41b
--- /dev/null
+++ b/docs/website/content/blog/always-on-top-style-parser.md
@@ -0,0 +1,45 @@
+---
+title: Always on Top and Style Parser
+slug: always-on-top-style-parser
+url: /blog/always-on-top-style-parser/
+original_url: https://www.codenameone.com/blog/always-on-top-style-parser.html
+aliases:
+- /blog/always-on-top-style-parser.html
+date: '2017-09-24'
+author: Shai Almog
+---
+
+
+
+It’s been a busy week with 3.7.3 released and a lot of new things. [Diamond](https://github.com/diamondobama) made several [PR’s](https://github.com/codenameone/CodenameOne/pulls) over the past couple of weeks but one interesting PR is an “always on top” feature for the simulator which is exactly what it sounds…
+
+This is very useful for me personally as it will allow me to film coding while showing the simulator floating on top (thanks Diamond!) but it should be super useful for everyone. You can inspect the code/debugging values while the simulator floats on top. You can activate it using the simulator menu option.
+
+### Style Parser
+
+Styles are a pretty complex and deeply ingrained subject in Codename One. It’s really hard to extend or modify this without breaking everything…
+
+Steve recently ventured on the surface and offered a new way in thru a String based style syntax that is a hybrid of CSS and Codename One logic. Don’t confuse this with the CSS plugin. The CSS plugin works statically during compile time and Codename One is “unaware” of its existence. The resulting file in the CSS file is a regular CSS file.
+
+This new style mode is builtin to Codename One and overrides styles defined in the theme. This is useful for the GUI builder where you might want to customize the appearance of a component without venturing into the theme and creating a new UIID.
+
+You can use this from code and might find some pretty cool hacks for it but this was built mostly to facilitate styling directly from the GUI builder. This can be used in the new GUI builder UI in the 3rd tab where you can now customize styles for components directly:
+
+
+
+Figure 1. Style customization from within the GUI builder
+
+This is a new feature so some things might now work for the 3.7.3 release. Please let us know in the [issue tracker](http://github.com/codenameone/CodenameOne/issues/)
+
+__ | In the current version there is a bug that might require you to close and reopen the UI for the styling to work
+---|---
+
+As a sidenote the new GUI builder and autolayout mode have improved significantly since 3.7.2 and are far more pleasurable to use!
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/analysis-google-moving-to-openjdk-what-that-really-means.md b/docs/website/content/blog/analysis-google-moving-to-openjdk-what-that-really-means.md
new file mode 100644
index 0000000000..09567fb2c4
--- /dev/null
+++ b/docs/website/content/blog/analysis-google-moving-to-openjdk-what-that-really-means.md
@@ -0,0 +1,280 @@
+---
+title: 'Analysis: Google Moving to OpenJDK, What That Really Means'
+slug: analysis-google-moving-to-openjdk-what-that-really-means
+url: /blog/analysis-google-moving-to-openjdk-what-that-really-means/
+original_url: https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html
+aliases:
+- /blog/analysis-google-moving-to-openjdk-what-that-really-means.html
+date: '2015-12-29'
+author: Shai Almog
+---
+
+
+
+I’ve been following the news breaking since yesterday as
+[a hacker news post](https://news.ycombinator.com/item?id=10803775)
+highlighted that an Android commit included OpenJDK files. This is amazing news and a huge step forward
+for Java, its still unclear if this is just another move or the inkling of a settlement between Google and
+Oracle but I’m very hopeful that this is indeed a settlement. So far Google wouldn’t comment on the
+court case and [
+whether it was settled since its still ongoing.](http://venturebeat.com/2015/12/29/google-confirms-next-android-version-wont-use-oracles-proprietary-java-apis/)
+**Disclaimer:** I worked at Sun/Oracle but had no internal knowledge of any of the things
+discussed here. The information here is collected from public sources, my understanding of the mentality of
+said companies & from our following the case at [Codename One](/) (we consulted IP lawyers quite a bit
+when we started and this case was always on our mind).
+
+I’ve read a lot of the comments in the reddit & hacker news threads and they seem to include a few upvotes
+on comments that are just plainly wrong here is a brief FAQ/informational on these common misquotes.
+
+#### Android will move to Hotspot/JIT
+
+That’s unlikely. Java compatibility and compliance with OpenJDK doesn’t require that you use the JIT that ships
+with it or any of its source code for that matter. Quite a few compliant implementations don’t.
+While Hotspot will probably beat ART for anything other than startup time performance on mobile is quite
+different than desktop performance. Battery life is more crucial than speed and I doubt hotspot is
+optimized for that.
+
+#### Swing/AWT/FX Will Finally Be Supported On Android
+
+There is no indication of that and it seems very unlikely.
+Google can be Java compliant by supporting a subset of Java and this is even easier thank to the modularity
+changes in Java 9. Swing/AWT/FX compliance complicate this whole process to a completely different level.
+
+#### Google Was At Fault
+
+There were several claims like this e.g. Google copied code etc.
+I don’t like the fact that Android isn’t Java compatible and forked, but I generally disagree with that statement.
+It might have been wrong “morally” to fork Java, but I don’t think it was wrong legally.
+In the discovery phase of the trial only one small method was shown to be a direct copy and the judge
+dismissed that as ridiculous.
+Google didn’t violate the trademarks of Java and the coffee cup which are an important tool to keep Java clean.
+E.g. they never claimed that Android runs Java, it runs Android/Dalvik and now ART. It compiles Java source code
+which is a big leap.
+The claim is about copyrighting the API, that’s a problematic claim since Google did use a clean room implementation
+of the public API’s. The supreme court effectively said that clean room implementations of public API’s are
+illegal!
+
+That’s a pretty bad thing since copyright is implicit. Its owned even if the person publishing the material doesn’t
+explicitly write that little (c) you see next to various types of work. So if you ever implemented an API you are
+now effectively using copyrighted code!
+
+Most programmers who understand this think that Google acted based on “fair use” which means they didn’t
+actually violate the rights of the copyright holder.
+
+#### Oracle is a greedy litigious company
+
+Not really.
+Oracle does sue companies but generally larger companies that can afford this, I’m unaware of them suing
+a startup or other small companies (feel free to correct me if I’m wrong here). It is far more profit driven than
+Sun was. I really loved Sun and loved working there, I can’t say the same about Oracle…
+But to be totally fair, Sun no longer exists in part because it was mismanaged and maybe not “greedy” enough.
+Having a strong “landlord” for Java might be disturbing in some regards but it has its advantages. I think
+anyone trying to show Oracle off as “evil” is plain wrong.
+
+#### This is like the Microsoft Lawsuit
+
+No.
+Microsoft was a Java licensee and took the code to create a non-compliant implementation. Google was
+a licensee but the Android division was not. The fact that Google was a licensee for Java didn’t factor into
+the trial to my non-lawyer knowledge.
+
+#### This is about Java Compatibility
+
+While Java compatibility is important and Google did do some damage there (not as much as the lawsuit did but
+still), this wasn’t the reason for the case.
+The lawsuit originally mentioned a 6bn USD figure for compensation and Google was willing to pay 100M USD
+to settle… This was like most lawsuits are about money.
+Its not necessarily bad to sue about money but this clearly cost more than it should have for both sides as
+it hurt Java in the market and that hurt two of its biggest users (Oracle & Google). Sun used to make
+a per device license fee for every J2ME phone sold, that was a huge bucket of money. I think Sun lost
+that revenue stream because it just neglected to update J2ME for more than a decade and when it finally did it
+was way too late.
+
+#### Is This Good For Java?
+
+Yes. Without a question!
+Some claim the lawsuit should have been decisively won by Google, I think that would have been great because
+as stated above I don’t think the copyright clause is good for the industry as a whole. But that ship has sailed
+and now the best thing for Java is ending the hostility and unifying behind one standard Java so its great.
+
+Furthermore, ending this hostility means Java proved its chops in court which is a huge milestone. Developers
+often have the justify-able backlash when the legal system is involved choosing to go with a technology that’s
+more open (e.g. WebAssembly). Technologies like that might have hidden elements that can be
+sued over that we aren’t even aware of e.g. just using a GIF file was ground for legal action a few years ago…
+This is problematic with more open standards as there isn’t a single “landlord” to carry the technology forward
+in such a court case.
+
+Java has seen quite a few days in court and one of the nice things about OpenJDK is that it includes a patent
+license provision. This is rare and really valuable, if Google and Oracle settle over OpenJDK it pretty much means
+we can all align behind it peacefully and litigation would become far less likely.
+
+Furthermore, I think this would be great for Android as it will eventually move to a newer more modern version
+of Java and might enjoy better tooling as a result. It would also mean that some optimizations that might have
+been avoided by Android’s Runtime due to potential patent litigation might be applied to ART leading to
+additional benefits.
+
+The reduced stress among Google developers about IP cleanliness could also boost the productivity at Google
+and allow the Android developers to focus on building a better product.
+
+#### What About Older Versions of Android?
+
+For those we will be able to use something like retrolambda like we do in Codename One.
+
+#### How Will This Affect Codename One?
+
+Right now we tried to avoid OpenJDK code as much as possible with the same basic thought pattern Google
+took of using a clean room implementation to protect ourselves from future IP claims.
+We are following this closely, if this happens we’ll align ourselves to be compliant with CLDC 8 which is
+a valid Java 8 subset. This shouldn’t require much works as we are already 50% there and
+[ParparVM](https://github.com/codenameone/CodenameOne/tree/master/vm)
+etc. should be trivial to move to that level of compliance.
+
+This would hopefully give us a path to move forward in a way that would keep everyone happy.
+
+#### Summary
+
+This is great news for all Java developers everywhere!
+Whether you work on Android, server, mobile or desktop!
+This could be the start of the long anticipated “peace process” or at least a ceasefire between Google & Oracle.
+This could allow us all to align behind one Java version eventually (taking into consideration the slow Android
+update process). It could help bring Java back into vogue with some developers who considered the closed
+nature of Java problematic.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Luix** — December 30, 2015 at 2:26 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22616))
+
+> Luix says:
+>
+> Re: “is it good for Java?” The main issue here is Java isn’t good enough for anyone. Most of the apps running in that resource hog are chucks of code blindly stolen from previous apps/programs. Most of the developers just steal the code and mend it to perform more or less decently, and when that’s not enough, they just simply demand more hardware.
+>
+> I don’t mean to start a flame war, but IMHO Google should ditch the Java approach all together and find a better replacement.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Shai Almog** — December 30, 2015 at 2:31 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22626))
+
+> Shai Almog says:
+>
+> You don’t mean to start a flame war but you post a “Java sux” comment in a Java blog. And with a Darth Vader avatar no less 😉
+>
+> Copy and paste == code reuse. Bad programmers exist everywhere. Java is actually pretty efficient (e.g. embedded etc.) and performant when compared to pretty much all alternatives.
+>
+> Google analyzed the options before picking Java, there are no realistic alternatives for Java and none are better performing.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Luix** — December 30, 2015 at 2:44 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22636))
+
+> Luix says:
+>
+> An excellent answer. I apologize, English isn’t my main language. Java *was* the best alternative when Android started walking its first steps, but then again, a lot of water has gone below that bridge.
+>
+> As a SysAdmin I deal with poorly written Java code all the time (i.e. the developers simply complain about their code running slow on expensive hardware because they don’t have enough resources). I’d like to see a path to better resource usage rather than expecting the system to grow in size. That’s just an unrealistic approach. I reckon it’s Google’s fault for not steering the development in that direction, but then again, Java might be the de facto standard, but it doesn’t mean it couldn’t be improved.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Shai Almog** — December 30, 2015 at 2:53 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22456))
+
+> Shai Almog says:
+>
+> We discussed this a bit in the hacker news thread. One of the problems with Java on the server/desktop is the lack of MVM (or isolates as they are called today) which allows multiple Java instances to share more state and thus take up less RAM/CPU. Back in the day this wasn’t a priority for Sun as it was selling big iron hardware and it would just mean “buy more hardware”.
+>
+> I totally see the issues you are running against, some Java apps are just ridiculous and its often hard for us to gauge what the hell is needed in terms of resources. One annoyance I had with servers was that the Xmx switch for the JVM to indicate the maximum memory has no value for the admin. It only allocates the memory visible to the Java application but not the memory I need to give to the server. So if I use -Xmx2gb the app might take up 2.5gb because of various VM overhead issues. This makes it pretty painful, but that’s an implementation problem more than an inherent issue in the Java language.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Luix** — December 30, 2015 at 3:01 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-21500))
+
+> Luix says:
+>
+> Again, I appreciate the time you took to make me a little less ignorant.
+>
+> Like you said, a language isn’t inherently bad or good, it’s the use we give to it what draws a positive/negative value.
+>
+> Anyway, I salute the step Google took to a open source implementation, and agree with you in your considerations about what that means in terms of growth for the community and the language itself. I just hope Google would tighten the development guidelines the way they did with the visual ones with Material Design.
+>
+> All I have left are my best wishes for the upcoming new year.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **bryan** — January 2, 2016 at 10:10 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22440))
+
+> bryan says:
+>
+> What you said makes no sense at all. Java the language is fine (unless your preference is functional languages), and the JVM (which as Shai has noted is NOT what Google use) works just fine also. Back in the day almost all feature phones had Java baked in, and given the hardware constraints, Java apps worked remarkably well (and worked even better with LWUIT – thanks Shai/Chen), so to say Java is intrinsically slow or a resource hog is nonsense.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Adam** — January 6, 2016 at 7:19 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22657))
+
+> Adam says:
+>
+> “While Hotspot will probably beat ART for anything other than startup time performance on mobile is quite different than desktop performance. Battery life is more crucial than speed and I doubt hotspot is optimized for that.”
+>
+> Are these really competing goals? Something that executes in fewer CPU cycles will result in longer battery life. AOT is always better than JIT for most things (unless they can only be known at runtime), but as far as a JIT engine goes, faster is better for everybody.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Shai Almog** — January 6, 2016 at 7:30 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22514))
+
+> Shai Almog says:
+>
+> Not quite so simple… we have two cases:
+>
+> a. Really complex calculations that take up 100% of the CPU. JIT has the advantage of determining all the branches that never change and optimizing them away. Inlining methods more aggressively etc.
+>
+> b. Most apps – cpu is needed for very short bursts and idle afterwards. JIT has plenty of CPU cycles left to optimize the short bursts to be much faster/smaller. On mobile devices this is good since it makes better use of CPU caches.
+>
+> AOT will always be slower than a good JIT since it has no way of optimizing away a branch statement that never changes. These are really expensive. Virtual method inlining (which leads to cross method optimizations) is also a HUGE benefit.
+>
+> Reducing battery usage is a different deal though and memory constraints on the device are also different. A jit can over optimize and regress. It can choose to use IDLE CPU to do various tasks which when optimized for power consumption it won’t. At Sun we had several very different JIT implementations, the mobile JIT’s had a far simpler architecture than hotspot. You even see this in the two modes hotspot features (server & client).
+>
+> Anyway, I digress. Its a bit more complex than that.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Adam** — January 6, 2016 at 7:46 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22257))
+
+> Adam says:
+>
+> Yes, a lot of that fits into my “unless they can only be known at runtime” parenthetical there, and I agree, there are limits to ahead of time compilation where a JIT can do much better.
+>
+> That being said, what on earth would the JIT be doing that is consuming otherwise idle CPU cycles that aren’t otherwise necessary tasks? Overly aggressive garbage collection? Maybe I’m only looking at this from an application level lens when I should be viewing from a virtual machine lens.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+
+### **Shai Almog** — January 6, 2016 at 7:55 pm ([permalink](https://www.codenameone.com/blog/analysis-google-moving-to-openjdk-what-that-really-means.html#comment-22664))
+
+> Shai Almog says:
+>
+> Think of a JIT as a profiler that runs on your device in your users hands.
+>
+> The profiler ran while the user used the application, since every user uses the app differently the JIT has an option to optimize based on how the user worked and idle is the perfect time to do it.
+>
+> Hotspot will further optimize but needs to decide when to stop and also when to revert an optimization that didn’t generate the right results (optimizations aren’t always clean cut).
+>
+> Mobile JIT’s aren’t as aggressive but they do something weird. They GC jitted code. E.g. if RAM is low or if a piece of code was used a few times and then no longer called then wasting RAM on compiled code is redundant.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanalysis-google-moving-to-openjdk-what-that-really-means.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/android-app-bundle-support.md b/docs/website/content/blog/android-app-bundle-support.md
new file mode 100644
index 0000000000..f3b6775d03
--- /dev/null
+++ b/docs/website/content/blog/android-app-bundle-support.md
@@ -0,0 +1,82 @@
+---
+title: Android App Bundle Support
+slug: android-app-bundle-support
+url: /blog/android-app-bundle-support/
+original_url: https://www.codenameone.com/blog/android-app-bundle-support.html
+aliases:
+- /blog/android-app-bundle-support.html
+date: '2021-05-08'
+author: Shai Almog
+description: We have added Android App Bundle support which will become the required
+ format for submitting apps to Google Play.
+---
+
+We have added Android App Bundle support which will become the required format for submitting apps to Google Play.
+
+A few months ago, we added Android App Bundle support and forgot to tell anyone... These things sometimes happen in a fast-moving startup… Well, better late than never.
+
+To try the App Bundle support, use the build hint: android.appBundle=true
+
+This will produce the regular APK and the app bundle file which you should be able to upload to Google.
+
+### What is Android App Bundle?
+
+For those who aren’t aware… Android App bundle is a new format designed by Google to replace the venerable APK.
+
+It isn’t much different but is generally better suited for splitting so different versions can be sent to different devices more easily.
+
+This makes sense for very fat applications, not so much for Codename One apps which are usually leaner than native Android apps.
+
+In August, Android App Bundle will become the required format for submitting to Google Play. In a couple of weeks we will flip the default so the app bundle build hint will be true implicitly.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved here for historical context. New discussion happens in the Discussion section below._
+
+
+### **Francesco Galgani** — June 6, 2021 at 12:57 pm ([permalink](https://www.codenameone.com/blog/android-app-bundle-support.html#comment-24464))
+
+> Francesco Galgani says:
+>
+> Since the aab format is not installable on an Android smartphone but can only be used to publish in the store, you will always continue to provide the apk format, right?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-app-bundle-support.html)
+
+
+### **Shai Almog** — June 6, 2021 at 2:01 pm ([permalink](https://www.codenameone.com/blog/android-app-bundle-support.html#comment-24466))
+
+> Shai Almog says:
+>
+> I won’t say “always” since things change but as long as AAB isn’t installable you will need an APK and we’ll provide it.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-app-bundle-support.html)
+
+
+### **Chris Vorster** — September 11, 2021 at 12:56 am ([permalink](https://www.codenameone.com/blog/android-app-bundle-support.html#comment-24479))
+
+> Chris Vorster says:
+>
+> Trying to upload a new version of the AAB on Google Dev Console results in signing error. Cannot find a way to fix this, tried support also.
+> “Your Android App Bundle is signed with the wrong key. Ensure that your App Bundle is signed with the correct signing key and try again. Your App Bundle is expected to be signed with the certificate with fingerprint:…”
+>
+> Can’t seem to find any solutions on how to deal with this.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-app-bundle-support.html)
+
+
+### **Lianna Casper** — September 11, 2021 at 4:12 am ([permalink](https://www.codenameone.com/blog/android-app-bundle-support.html#comment-24480))
+
+> Lianna Casper says:
+>
+> You need to pick the right keystore in Codename One Settings. How did you sign the app you first uploaded?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-app-bundle-support.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/android-build-target-27-migration.md b/docs/website/content/blog/android-build-target-27-migration.md
new file mode 100644
index 0000000000..62c5126866
--- /dev/null
+++ b/docs/website/content/blog/android-build-target-27-migration.md
@@ -0,0 +1,299 @@
+---
+title: Android Build Target 27 Migration
+slug: android-build-target-27-migration
+url: /blog/android-build-target-27-migration/
+original_url: https://www.codenameone.com/blog/android-build-target-27-migration.html
+aliases:
+- /blog/android-build-target-27-migration.html
+date: '2018-03-27'
+author: Shai Almog
+---
+
+
+
+A while back [Google announced](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html) that starting in August 2018 they will no longer accept applications targeting API levels below 26. With that in mind we plan to migrate our builds to use API level 27 which brings with it a lot of great new features but will probably break some things as we go through the migration. Please read this post carefully, I’ll try to cover everything.
+
+Notice that this announcement means that we will need to start updating the API levels every year which is a much faster pace.
+
+I’ve constructed this post as a set of questions/answers.
+
+### What’s an API Level?
+
+Every time Google releases a new version of Android it updates the API level e.g. currently Oreo (8.1) is API level 27.
+
+When we build a native Android application we need to declare the “target” this means we compiled the project against this given API level. This is a double edged sword… When we pick a higher API level we can target new features of newer OS’s but we are also subject to new restrictions and changes.
+
+E.g. when we migrated to API level 23 we had to change the way permissions are processed in applications. For Codename One code this was mostly seamless but if you relied on native code this sometimes triggered issues.
+
+API level 27 can impact things such as background behavior of your application and can break some cn1libs/native code you might have in place.
+
+FYI this is also explained in this [article](https://arstechnica.com/gadgets/2017/12/google-fights-fragmentation-new-android-features-to-be-forced-on-apps-in-2018/) from Ars.
+
+### Will this Work for Older Devices?
+
+The target API level doesn’t restrict older devices. For that we have a separate minimum target device and it indicates the lowest API level we support. Currently the default is 15 (Android 4.0.3 – Ice Cream Sandwich) but you can probably set it as low as 9 (Android 2.3 – Gingerbread) as long as you test the functionality properly and disable Google Play Services.
+
+See [this](https://developer.android.com/guide/topics/manifest/uses-sdk-element.html) for a table of all the API levels from Google.
+
+### What API Level do we use Now?
+
+That depends on your app. Our default is 23 but some cn1libs set the API level to 25.
+
+We chose to migrate slowly as level 23 is generally good and stable.
+
+### Test This Now!
+
+**Test API level 27 right now before we flip the switch!**
+
+This is important as we want to iron out bugs/regressions before they impact everyone. You can enable this seamlessly by setting the build hint: `android.buildToolsVersion=27`
+
+__ | Remove this build hint after testing, otherwise when we migrate to a newer version later on it might fail!
+---|---
+
+This will implicitly set a lot of other values including the target level and it will change the gradle version from 2.12 to 4.6.
+
+#### Other Benefits.
+
+By flipping this switch the build should now work on Android Studio 3.x out of the box without the changes listed in [this tip](/blog/tip-include-source-android-studio-3.html). We also plan to enable other things in the resulting project such as using Googles builtin Java 8 support instead of ours (this isn’t enabled yet).
+
+This will mean that native Android code would be able to use Java 8 features. Notice that this currently applies to the native interfaces only and not to the code in the Codename One implementation.
+
+This should make it easier to work with some 3rd party libraries that already moved forward.
+
+### When Will this Happen?
+
+The 27 build target is available now using the build hint: `android.buildToolsVersion=27`.
+
+Currently we are aiming to flip the switch by May. This might be pushed to a different date based on responses/feedback on the current status. We want to have enough time ahead so the July release of Codename One 5.0 (Social) will use this.
+
+### Is this a Good Thing?
+
+I think it is. It prevents stagnation within the appstore.
+
+I still see apps in the stores that target Gingerbread (API level 9). That’s a problem both visually & in terms of permissions/security.
+
+I don’t think it will do enough to combat fragmentation. Google will need to change a lot more to fix that pickle but it’s a baby step in the right direction.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Francesco Galgani** — April 6, 2018 at 9:56 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23555))
+
+> Francesco Galgani says:
+>
+> I’ve done some tests with “android.buildToolsVersion=27” in Android 7 and Android 5 devices and I didn’t notice any difference 🙂
+> However, I don’t understand why the target API level doesn’t restrict older devices: if the API 27 is for Android 8.1, how is it possible that the older devices are supported? Is it possible because Codename One build servers don’t generate code that is supported only by recent devices?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Shai Almog** — April 7, 2018 at 4:42 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23648))
+
+> Shai Almog says:
+>
+> Good to hear.
+>
+> This is a feature from native Android not us. There are 2 different values: minimum API level and Target API level.
+>
+> When we use a feature from API 27 we check if the device supports API 27 first then call it. This means it will still work on an API 15 device without a problem.
+>
+> This is a “statement” from the app to the runtime environment saying we tested on an Android 8 device. That means that Google will enable small incompatible changes to Android once this is set. A good example is background behavior which will now be more aggressive.
+>
+> E.g. In API 23 (Andoid 6) Google introduced a new permission system.
+>
+> So if you had an Android 5 device and installed an app compiled with target 23 it would still work and show the permission prompt during install.
+>
+> If you have an Android 6 device and installed an old app (target smaller than 23) it would behave like an Android 5 device and show permissions during install.
+>
+> However, if both the app and the device are API 23 or newer the app would install instantly and prompt for permissions in runtime.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Denis** — May 12, 2018 at 8:45 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23780))
+
+> Denis says:
+>
+> Hi Shai,
+>
+> I have put android.buildToolsVersion=27 in build hints, however when I upload apk to Play Console, it warns that app still targets API 23, could you please take a look at screenshots and advise ?
+>
+> Thanks,
+> Denis
+>
+> [https://uploads.disquscdn.c…]() [https://uploads.disquscdn.c…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Shai Almog** — May 13, 2018 at 4:25 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23692))
+
+> Shai Almog says:
+>
+> Hi,
+> what do you have within your [codenameone_settings.proper…](?) It looks like this isn’t passing through.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Denis** — May 13, 2018 at 6:38 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23725))
+
+> Denis says:
+>
+> Hi,
+> codename1.arg.android.buildToolsVersion=27 is there, so it looks correct as I understand, please confirm
+> Thanks
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Shai Almog** — May 14, 2018 at 4:25 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-21475))
+
+> Shai Almog says:
+>
+> Hi,
+> yes. But other build hints might collide with this functionality so are there other android.* build hints in the file?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Denis** — May 14, 2018 at 7:24 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23556))
+
+> Denis says:
+>
+> Hi Shai,
+>
+> yes, that makes, I don’t know why I haven’t post entire file right away ))
+> here it is, take a look please
+>
+> [android.playService.ads]()=true
+> codename1.android.keystore=XXXXXXXXXXXXXXXXXXXXX
+> codename1.android.keystoreAlias=XXXXXXXXXXXXXXXX
+> codename1.android.keystorePassword=XXXXXXXXXXXXX
+> codename1.arg.android.buildToolsVersion=27
+> codename1.arg.android.debug=false
+> codename1.arg.android.licenseKey=XXXXXXXXXXXXXXX
+> codename1.arg.android.release=true
+> codename1.arg.android.statusbar_hidden=true
+> codename1.arg.android.xapplication=
+> codename1.arg.ios.add_libs=AdSupport.framework;SystemConfiguration.framework;CoreTelephony.framework
+> codename1.arg.ios.newStorageLocation=true
+> codename1.arg.ios.objC=true
+> codename1.arg.ios.pods=,Firebase/Core,Firebase/AdMob
+> codename1.arg.ios.pods.platform=,7.0
+> codename1.arg.ios.pods.sources=,
+> codename1.arg.ios.statusbar_hidden=true
+> codename1.arg.java.version=8
+> codename1.displayName=XXXXXXXXXXXXXXXXXXXXXXXXXX
+> codename1.icon=icon.png
+> codename1.ios.certificate=
+> codename1.ios.certificatePassword=
+> codename1.ios.provision=
+> codename1.j2me.nativeTheme=nativej2me.res
+> codename1.languageLevel=5
+> codename1.mainName=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+> codename1.packageName=com.manyukhin.XXXXXXXXXXXX
+> codename1.rim.certificatePassword=
+> codename1.rim.signtoolCsk=
+> codename1.rim.signtoolDb=
+> codename1.secondaryTitle=XXXXXXXXXXXXXXXXXXXXXXX
+> codename1.vendor=Denis Manyukhin
+> codename1.version=1.11
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Shai Almog** — May 15, 2018 at 4:08 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23790))
+
+> Shai Almog says:
+>
+> Thanks,
+> it looks like you are missing codename1.arg. before the [android.playService.ads]() but that’s unrelated.
+>
+> Looking again at the code I think you might need to explicitly specify android.sdkVersion=27 for this to work.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Denis** — May 15, 2018 at 4:25 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23960))
+
+> Denis says:
+>
+> Thanks Shai,
+> Soo, I should put sdkVersion build hint in [codenameone_settings.proper…](), right ?
+>
+> also it’s better to move [android.playService.ads]() below Android build hints, is that correct ?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Shai Almog** — May 15, 2018 at 4:48 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-21644))
+
+> Shai Almog says:
+>
+> If you edit [codenameone_settings.proper…]() you need to prefix it with codename1.arg. I suggest using the Codename One Setting UI under “Build Hints” to edit these and not edit the file directly.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Denis** — May 15, 2018 at 6:44 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23699))
+
+> Denis says:
+>
+> I have added android.sdkVersion=27 to Build hints via corresponding UI, it appears as codename1.arg.android.sdkVersion=27 in [codenameone_settings.proper…](),
+> but still the same warning in Google Play Console, app targeted to API 23, any ideas ?
+>
+> also I can’t see “[android.playService.ads]()=true” in Build Hints UI, it only appears in [codenameone_settings.proper…](), is it ok ?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Denis** — May 15, 2018 at 9:20 pm ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23716))
+
+> Denis says:
+>
+> have you meant android.targetSDKVersion build hint ?
+> if not, may be it worth to set android.targetSDKVersion value explicitly ?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Shai Almog** — May 16, 2018 at 5:56 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23767))
+
+> Shai Almog says:
+>
+> Does targetSDKVersion solve this issue?
+> There is a bit of a mess of build hints here, we should clean it up a bit and ideally expose them in the Android section of Codename One Settings.
+>
+> Only things with the codename1.arg. prefix will appear in the build hints UI so that flag is effectively ignored.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Denis** — May 16, 2018 at 6:16 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23693))
+
+> Denis says:
+>
+> yes, android.targetSDKVersion solved the issue, no target API warnings, wondering if I shall keep android.sdkVersion and android.buildToolsVersion records in build hints
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+
+### **Shai Almog** — May 17, 2018 at 11:05 am ([permalink](https://www.codenameone.com/blog/android-build-target-27-migration.html#comment-23931))
+
+> Shai Almog says:
+>
+> We’ll switch all of these to default to 27 probably next weekend. I want to give this enough time before we release 5.0 in July.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-build-target-27-migration.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/android-gradle-build-status-minor-changes.md b/docs/website/content/blog/android-gradle-build-status-minor-changes.md
new file mode 100644
index 0000000000..a4029a2999
--- /dev/null
+++ b/docs/website/content/blog/android-gradle-build-status-minor-changes.md
@@ -0,0 +1,65 @@
+---
+title: Android Gradle Build Status & Minor Changes
+slug: android-gradle-build-status-minor-changes
+url: /blog/android-gradle-build-status-minor-changes/
+original_url: https://www.codenameone.com/blog/android-gradle-build-status-minor-changes.html
+aliases:
+- /blog/android-gradle-build-status-minor-changes.html
+date: '2016-02-08'
+author: Shai Almog
+---
+
+
+
+I’ve been remarkably busy working on issues and documentation so I neglected an important announcement I
+had to make. Over the weekend we flipped the default build from gradle back to ant. So effectively if you don’t set
+any build hint the behavior will be `android.gradle=false` which should work fine for most of you. This is temporary but
+we felt it was necessary as a stopgap measure.
+
+In other news it seems that fixing the Codename One documentation is like diving into a bottomless pit.
+When we started this effort the developer guide was 300 pages it is now approaching 500 pages and we
+aren’t close to half way thru…
+
+This doesn’t even cover all the work we did with refining the JavaDocs and there is a lot of work that needs doing
+on that side of the fence.
+
+During this time I’ve made a conscious effort not to do anything significant that isn’t documentation writing but
+some code had to go thru. Specifically things related to syntax that needed doing for the developer guide.
+
+### CheckBox Toggle Syntax
+
+Up until now we had terse syntax for creating a toggle button for a `RadioButton` but we didn’t have anything
+like that for the `CheckBox`. So we added a couple of methods:
+
+ * [createToggle(Image icon)](https://www.codenameone.com/javadoc/com/codename1/ui/CheckBox.html#createToggle-com.codename1.ui.Image-)
+
+ * [createToggle(String text)](https://www.codenameone.com/javadoc/com/codename1/ui/CheckBox.html#createToggle-java.lang.String-)
+
+ * [createToggle(String text, Image icon)](https://www.codenameone.com/javadoc/com/codename1/ui/CheckBox.html#createToggle-java.lang.String-com.codename1.ui.Image-)
+
+### ButtonGroup Shortcut
+
+Up until now creating a `RadioButton` required adding it to a `ButtonGroup` which was tedious.
+
+To solve this we added a varargs
+[addAll(Component…)](https://www.codenameone.com/javadoc/com/codename1/ui/ButtonGroup.html#addAll-com.codename1.ui.RadioButton…-)
+method as well as a [varargs constructor](https://www.codenameone.com/javadoc/com/codename1/ui/ButtonGroup.html#ButtonGroup-com.codename1.ui.RadioButton…-).
+
+### ComponentGroup enclose
+
+`ComponentGroup` didn’t have an `enclose` method which is one of those things that beg for a fix since its **the**
+`Container` for that sort of API.
+
+So we added two enclose methods:
+
+ * [enclose(Component…)](https://www.codenameone.com/javadoc/com/codename1/ui/ComponentGroup.html#enclose-com.codename1.ui.Component…-)
+
+ * [encloseHorizontal(Component…)](https://www.codenameone.com/javadoc/com/codename1/ui/ComponentGroup.html#encloseHorizontal-com.codename1.ui.Component…-)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/android-migration-tool.md b/docs/website/content/blog/android-migration-tool.md
new file mode 100644
index 0000000000..d014cc73c2
--- /dev/null
+++ b/docs/website/content/blog/android-migration-tool.md
@@ -0,0 +1,79 @@
+---
+title: Android Migration Tool
+slug: android-migration-tool
+url: /blog/android-migration-tool/
+original_url: https://www.codenameone.com/blog/android-migration-tool.html
+aliases:
+- /blog/android-migration-tool.html
+date: '2016-07-26'
+author: Shai Almog
+---
+
+
+
+It’s tough to pick up a new toolchain like Codename One. There’s so much to learn…
+A lot of our developers come from the Android world and even though Codename One is much simpler than Android porting the first app to Codename One is still painful.
+
+We wanted to simplify this process since the day we launched Codename One but as we [explained before](/blog/why-we-dont-import-android-native-code.html), this isn’t simple and the results would “underwhelm”.
+
+We decided that “underwhelming” isn’t always a bad place to start when you are doing open source work. With that in mind Steve [created an open source project](https://github.com/shannah/cn1-android-importer) to scaffold a new Codename One project from an existing Android native project.
+
+Notice my choice of words, I chose scaffold instead of migrate. This will not turn an Android project into a Codename One project but rather make the process of getting started slightly easier. It migrates the images & strings.
+
+It creates GUI builder files (using the new GUI builder) for every layout XML file. Notice that the layout isn’t replicated properly and neither is the proper styling.
+
+These differ a lot between Android and Codename One and would require at least 6 months of intense work to get right.
+
+Copying the layout seems deceptively easy on the surface but Android layouts differ considerably. We’d love to simplify that but the level of effort required is beyond our limited resources. We can’t justify the effort without a sense of demand…
+
+In the current version we don’t touch the source or the manifest at all but we could address both of these to some degree as part of the work.
+
+### Moving This Forward
+
+That’s where you come in. File issues, RFE’s and let us know that you want progress on this.
+
+Fork and contribute to Steve’s project and provide samples that we should improve.
+
+Let your friends know about this project and raise community awareness around it!
+
+We don’t want to invest significant developer resources on something that won’t gain developer traction so we need your help to get this project off the ground.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Gareth Murfin** — July 27, 2016 at 3:09 pm ([permalink](https://www.codenameone.com/blog/android-migration-tool.html#comment-22786))
+
+> Gareth Murfin says:
+>
+> What a great idea, many Android devs looking to produce iOS ports end up thinking about using Codename One. As you say resources are so very different, I found learning Android GUI dev much harder than learning CN1 GUI dev (but previously I was into Swing, J2ME, LWUIT etc). I think one of the main paradigm shifts that is hard to learn is the lack of “activities”. That is in Android each screen has its own class and it starts to feel nice and correct (more OO/modular or something :)) – and when you go to CN1 it is very strange to have everything more “old school” in one or 2 classes. If it were possible it would be good if each screen in cn1 could actually be a separate class, so when you create an event for postShow or something it doesnt go into statemachine but a class called for example Splash(), and with a method postMain() in there. This would make it far easier to navigate projects and understand them (new coders have been scared of even looking at my gargantuan statemachines, preferring to do a rewrite(!)). Just a suggestion of course, and we could easily do this ourself by simply making calls from StateMachine to custom classes we can make for each screen, which is actually what I am planing on doing in my next cn1 app. Current I mostly have one large statemachine, another class holding the business logic that is called on from statemachine, and then a pile of POJOs.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-migration-tool.html)
+
+
+### **bryan** — July 27, 2016 at 9:08 pm ([permalink](https://www.codenameone.com/blog/android-migration-tool.html#comment-22906))
+
+> bryan says:
+>
+> A class per screen/form is the “new” way to do CN1, and the way the new GUI builder works, so this porting tool would do that.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-migration-tool.html)
+
+
+### **Shai Almog** — July 28, 2016 at 4:18 am ([permalink](https://www.codenameone.com/blog/android-migration-tool.html#comment-22706))
+
+> Shai Almog says:
+>
+> Yep. I mentioned this uses the new GUI builder so it’s one form class per layout.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fandroid-migration-tool.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/android-push-changes.md b/docs/website/content/blog/android-push-changes.md
new file mode 100644
index 0000000000..a84cd35df8
--- /dev/null
+++ b/docs/website/content/blog/android-push-changes.md
@@ -0,0 +1,63 @@
+---
+title: Android Push Changes
+slug: android-push-changes
+url: /blog/android-push-changes/
+original_url: https://www.codenameone.com/blog/android-push-changes.html
+aliases:
+- /blog/android-push-changes.html
+date: '2024-06-08'
+author: Steve Hannah
+---
+
+
+
+We have made some upgrades to our push notification server API. If you deploy apps to Android and use push notifications, you will need to make a small change to the server-side code that sends the HTTP request to our push server. If you do not send push notifications to Android devices, you can ignore this PSA.
+
+## The Short Version
+
+Our Push API now requires a JSON key instead of the old FCM API key. For example, the following is a template for a push notification, (copied from the [push cheatsheet](https://www.codenameone.com/files/push-cheatsheet.pdf)).
+
+
+ https://push.codenameone.com/push/push?token=PUSH_TOKEN
+ &device=DEVICE_ID1&device=DEVICE_ID2&...&device=DEVICE_IDN
+ &type=PUSH_TYPE{1|2|3|4|5|99|100|101}
+ &auth=FCM_SERVER_API_KEY
+ &certPassword=ITUNES_CERT_PASSWORD
+ &cert=ITUNES_CERT_URL
+ &body=MESSAGE_BODY
+ &production=ITUNES_PRODUCTION_PUSH{true|false}
+ &sid=WNS_SID
+ &client_secret=WNS_CLIENT_SECRET
+
+Currently, the `auth` parameter will be set to an FCM API key, it might look something like `auth=AAAABbbbCccDddEeeeFfffGGggHhhhIiiiJjjjKkkkLlllMmmmNnnnOooPppQqqRrrrSsssTttUuuuVvvvWxxxYyyyZzzz`
+
+You will need to change this parameter to be a URL (reachable by the Codename One push server) to a the JSON key for your service. It will look something like:
+
+
+ auth=https%3A%2F%2Fexample.com%2Fsecret%2Fpath%2Fto%2Fservice-account-file.json
+
+You can find instructions on how to generate this `service-account-file.json` (your JSON key) in the Firebase documentation [here](https://firebase.google.com/docs/cloud-messaging/auth-server#provide-credentials-manually). The following instructions are copied from there:
+
+**To generate a private key file for your service account:**
+
+> In the Firebase console, open **Settings > [Service Accounts](https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk)**.
+> Click **Generate New Private Key** , then confirm by clicking **Generate Key**.
+> Securely store the JSON file containing the key.
+
+> ## The Slightly Longer Version
+
+Google deprecated their legacy FCM APIs on June 20, 2023, and will be removing them on June 21, 2024. Our push servers use this API for all push notifications to Android devices, so we were required to [migrate ](https://firebase.google.com/docs/cloud-messaging/migrate-v1)to their new “v1 HTTP API”. This migration involved some non-trivial changes to our push infrastructure, including, but not limited to, changing from using an FCM API key for authentication, to using OAuth2.
+
+The OAuth2 authentication is more complicated than the old API key flow, as it involves multiple pieces of information, including the client ID, client secret, and project ID. To simplify this, Google encapsulates all of these credentials inside a single JSON file which can be used in an opaque manner via its Firebase SDKs.
+
+On our side, we wanted to avoid unnecessary changes to our API to make this transition as seamless as possible for our users, so we haven’t added or removed any parameters from the request. We just changed the `auth` parameter to include the URL to your `service-account-file.json`, instead of an FCM API key. We chose not to include the whole JSON file contents in each request because the JSON file tends to be quite large and presents unnecessary a network overhead.
+
+We may iterate on this approach in a non-breaking way based on user feedback.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/androids-permissions.md b/docs/website/content/blog/androids-permissions.md
new file mode 100644
index 0000000000..b6a00c6e68
--- /dev/null
+++ b/docs/website/content/blog/androids-permissions.md
@@ -0,0 +1,63 @@
+---
+title: Androids Permissions
+slug: androids-permissions
+url: /blog/androids-permissions/
+original_url: https://www.codenameone.com/blog/androids-permissions.html
+aliases:
+- /blog/androids-permissions.html
+date: '2014-03-23'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+One of the annoying tasks when programming native Android applications is tuning all the required permissions to match your codes requirements, when we started Codename One we aimed to simplify this. Our build server automatically introspects the classes you sent as part of the build and injects the right set of permissions required by your app.
+
+However, sometimes you might find the permissions that come up a bit confusing and might not understand why a specific permission came up. This maps Android permissions to the methods/classes in Codename One that would trigger them:
+
+android.permission.WRITE_EXTERNAL_STORAGE – this permission appears by default for Codename One applications, since the File API which is used extensively relies on it. You can explicitly disable it using the build argument android.blockExternalStoragePermission=true, notice that this is something we don’t test and it might fail for you on the device.
+
+android.permission.INTERNET – this is a hardcoded permission in Codename One, the ability to connect to the network is coded into all Codename One applications.
+
+android.hardware.camera & android.permission.RECORD_AUDIO – are triggered by com.codename1.Capture
+
+android.permission.RECORD_AUDIO – is triggered by MediaManager.createMediaRecorder() & Display.createMediaRecorder()
+
+android.permission.READ_PHONE_STATE – is triggered by com.codename1.ads package, com.codename1.components.Ads, com.codename1.components.ShareButton, com.codename1.media, com.codename1.push, Display.getUdid() & Display.getMsisdn(). This permission is required for media in order to suspend audio playback when you get a phone call.
+
+android.hardware.location, android.hardware.location.gps, android.permission.ACCESS_FINE_LOCATION, android.permission.ACCESS_MOCK_LOCATION & android.permission.ACCESS_COARSE_LOCATION – map to com.codename1.maps & com.codename1.location.
+
+package.permission.C2D_MESSAGE, com.google.android.c2dm.permission.RECEIVE, android.permission.RECEIVE_BOOT_COMPLETED – are requested by the com.codename1.push package
+
+android.permission.READ_CONTACTS – triggers by the package com.codename1.contacts & Display.getAllContacts().
+
+android.permission.VIBRATE – is triggered by Display.vibrate() and Display.notifyStatusBar()
+
+android.permission.SEND_SMS – is triggered by Display.sendSMS()
+
+android.permission.WAKE_LOCK – is triggered by Display.lockScreen() & Display.setScreenSaverEnabled()
+
+android.permission.WRITE_CONTACTS – is triggered by Display.createContact(), Display.deleteContact(), ContactsManager.createContact() & ContactsManager.
+
+deleteContact()
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/animated-gif-support.md b/docs/website/content/blog/animated-gif-support.md
new file mode 100644
index 0000000000..66f6f0ac04
--- /dev/null
+++ b/docs/website/content/blog/animated-gif-support.md
@@ -0,0 +1,129 @@
+---
+title: Animated Gif Support
+slug: animated-gif-support
+url: /blog/animated-gif-support/
+original_url: https://www.codenameone.com/blog/animated-gif-support.html
+aliases:
+- /blog/animated-gif-support.html
+date: '2017-08-07'
+author: Shai Almog
+---
+
+
+
+So you know how you write a blog post just before you go on vacation, press publish and never check that it actually got published… Funny thing, that’s exactly what I did and the blog post mentioning that I was on “vacation” for a couple of weeks never got published. Anyway, other people have been busy while I was “away” but I got a couple of things done too including animated gif support.
+
+Before we get to that Steve did a lot of work on Mac retina display support. This is a HUGE leap in usability if you use a retina Mac. It makes the iPhone 3gs skin tiny but you can now use the iPhone 5 skin without scaling… It looks great and uses the pixels on these Macs really well.
+
+I also released a new [cn1lib that implements animated GIF support](https://github.com/codenameone/AnimatedGifSupport/) in Codename One without the resource file hack. It’s still not something I would recommend as animated gifs can be pretty expensive in terms of resources but you can still use it to get a pretty decent animation.
+
+One of the cool things is that this works as a plug in image and you should be able to use it in most places where image works. There are caveats though. E.g. you can’t use it as a native map marker as that image is passed to native. But other than such API’s it should work in labels and even in background image styles, although I would suggest avoiding the latter as it would be a memory/battery drain.
+
+The library is in the extensions section and you can use it like this:
+
+
+ Form hi = new Form("Gif", new BorderLayout());
+
+ try {
+ hi.add(CENTER, new ScaleImageLabel(GifImage.decode(getResourceAsStream("/giphy-downsized.gif"), 1177720)));
+ } catch(IOException err) {
+ log(err);
+ }
+ hi.show();
+
+Notice the following:
+
+ * `GifImage.decode` can throw an `IOException`
+
+ * It accepts an `InputStream` and the length of the input stream so you need to know the size in advance
+
+ * It returns a `GifImage` which is a subclass of `Image`
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **salah Alhaddabi** — August 10, 2017 at 1:01 pm ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-23745))
+
+> salah Alhaddabi says:
+>
+> Thanks a lot Shai.
+>
+> So does this mean that the image will be animated continously??
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+
+### **Shai Almog** — August 11, 2017 at 7:22 am ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-23653))
+
+> Shai Almog says:
+>
+> It will loop based on the loop settings in the GIF itself. GIF’s contain a loop count. If it’s 0 it means looping forever.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+
+### **Francesco Galgani** — August 16, 2017 at 5:19 pm ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-23444))
+
+> Francesco Galgani says:
+>
+> Thank you 🙂
+> How are the various densities managed by animated GIFs? Is there any multi-image equivalent for GIF?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+
+### **Shai Almog** — August 17, 2017 at 4:39 am ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-24151))
+
+> Shai Almog says:
+>
+> We don’t. GIF has no density support so it can only be scaled. Using an approach like multi-image with GIF would be prohibitive as the file size will balloon. GIF’s are huge enough as it is.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+
+### **Francesco Galgani** — August 17, 2017 at 9:39 am ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-23637))
+
+> Francesco Galgani says:
+>
+> Mmm… is there any way to get the right animated GIF size using an external service such as Cloudinary? I’ve never used it, so I don’t know if it supports animated GIFs.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+
+### **Shai Almog** — August 18, 2017 at 5:56 am ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-23418))
+
+> Shai Almog says:
+>
+> I don’t know. I’m not familiar with that.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+
+### **Rainer** — August 23, 2017 at 7:52 pm ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-24219))
+
+> Rainer says:
+>
+> Hello! I tried the sample code with an animated gif, but nothing appears with the simulator
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+
+### **Shai Almog** — August 24, 2017 at 9:04 am ([permalink](https://www.codenameone.com/blog/animated-gif-support.html#comment-23695))
+
+> Shai Almog says:
+>
+> Do you see any error in the console?
+> Have you tried with a different gif file?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fanimated-gif-support.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/announcing-coderad-2-0-preview.md b/docs/website/content/blog/announcing-coderad-2-0-preview.md
new file mode 100644
index 0000000000..2c61acb3fa
--- /dev/null
+++ b/docs/website/content/blog/announcing-coderad-2-0-preview.md
@@ -0,0 +1,182 @@
+---
+title: Announcing CodeRAD 2.0 Preview
+slug: announcing-coderad-2-0-preview
+url: /blog/announcing-coderad-2-0-preview/
+original_url: https://www.codenameone.com/blog/announcing-coderad-2-0-preview.html
+aliases:
+- /blog/announcing-coderad-2-0-preview.html
+date: '2021-08-13'
+author: Steve Hannah
+description: We are proud to announce the immediate availability of the CodeRAD 2.0
+ developer preview. CodeRAD is a modern MVC framework for building truly native,
+ mobile-first, pixel-perfect applications in Java and Kotlin.
+---
+
+We are proud to announce the immediate availability of the CodeRAD 2.0 developer preview. CodeRAD is a modern MVC framework for building truly native, mobile-first, pixel-perfect applications in Java and Kotlin.
+
+
+
+CodeRAD 2 builds upon the solid foundation of CodeRAD 1.0, and adds several new features aimed at increasing component reuse, and improving developer experience.
+
+### Declarative View Syntax
+
+First among the long list of new features included in CodeRAD 2 is the ability to write your **View** classes in XML.
+
+For example, consider this simple login form component written in Java:
+
+```java
+
+ package com.example.mybareapp;
+
+import com.codename1.ui.*;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.layouts.FlowLayout;
+
+public class LoginForm extends Container {
+ public MyJavaForm() {
+ super(BoxLayout.y());
+
+ Label loginHeading = new Label("Login");
+ loginHeading.setUIID("LoginHeading");
+
+ TextField username = new TextField();
+ username.setMaxSize(30);
+ username.setConstraint(TextField.USERNAME);
+ username.setUIID("LoginField");
+ username.setHint("Enter username");
+ username.getHintLabel().setUIID("LoginFieldHint");
+
+ TextField password = new TextField();
+ password.setConstraint(TextField.PASSWORD);
+ password.setUIID("LoginField");
+ password.setHint("Enter password");
+ password.getHintLabel().setUIID("LoginFieldHint");
+
+ Label usernameLabel = new Label("Username:");
+ usernameLabel.setUIID("LoginFieldLabel");
+
+ Label passwordLabel = new Label("Password:");
+ passwordLabel.setUIID("LoginFieldLabel");
+
+ Button login = new Button("Login");
+ login.setUIID("LoginButton");
+
+ Button reset = new Button("Reset");
+ reset.setUIID("ResetButton");
+
+ Container buttons = FlowLayout.encloseCenter(
+ reset,
+ login
+ );
+
+ addAll(loginHeading,
+ usernameLabel,
+ username,
+ passwordLabel,
+ password,
+ buttons
+ );
+ }
+}
+
+
+```
+
+You could write the same component as a CodeRAD view using the following:
+
+```xml
+
+ xml version=1.0" ?
+
+ Login
+
+ Username:
+
+
+ Password:
+
+
+
+ Reset
+ Login
+
+
+
+
+```
+
+The XML view is automatically transformed into a Java class by the CodeRAD annotation processor so that performance is on par with the hand-coded **Java** version.
+
+
+This example demonstrates the primary benefit of using XML as a language for writing view classes: It is **declarative**! The XML representation maps directly to way the view will be presented in the app.
+
+
+This example is just the tip of the iceberg, however, as CodeRAD’s XML views also include many other features aimed at making your development experience as smooth as possible. They have built-in support for property binding, for example, making it easy to maintain a clean separation between your **View** and **Controller** logic.
+
+### Hot Reload
+
+When building user interfaces, the biggest pain point, by far, is the “test-edit-reload” cycle. e.g. Load app in simulator, add label to your form, reload app in simulator, navigate back to form, repeat…
+
+Having to wait while the app is recompiled, and the simulator is reloaded each time you make a change is an excruciating productivity killer.
+
+CodeRAD 2 takes a giant step toward alleviating this problem with its introduction of **Hot Reload**. When this feature is enabled, the simulator will update in near real time (1-2 second delay) as you make changes to your application source.
+
+There are two modes for Hot Reload:
+
+
+1. ****Reload Simulator**** – This will restart your app at your “start form” when changes are detected.
+
+
+2. ****Reload Current Form**** – This will restart your app, and automatically load the current Form, to save you from having to manually navigate there.
+
+
+
+## Note
+
+> This is actually a pseudo "hot" reload, as it restarts your app rather than just patching code in place like the existing "Apply code changes" feature in most Java IDEs does. The "apply code changes" feature is very limited as it doesn’t apply to code that has already been run in your app, and it doesn’t support things like adding methods or classes. If you are trying to test changes to a UI form using "apply code changes", you would typically need to navigate away from your form, and navigate back after code changes are applied to see the changes. And this would still be insufficient in cases where the UI depends on "bootstrap" code in the app. A full app restart is necessary to reliably see the result of code changes, and this is what CodeRAD’s hot reload feature provides.
+
+### CodeRAD 2 Intro Video
+
+This introduction to CodeRAD 2 video is a good starting point if you want to learn more about the features and concepts of CodeRAD.
+
+### Getting Started
+
+We have added a “CodeRAD (MVC) Starter Project” option to the [Codename One initializr](https://start.codenameone.com/), which will give you a starting point for developing apps with CodeRAD 2.
+
+
+
+The "Tweet App" template is also a CodeRAD 2 project that provides a twitter-style app template, with login forms, and a tweet list view.
+
+### Learn More
+
+We have developed a wealth of resources to help you get started with CodeRAD. After watching the [intro Video](https://youtu.be/x7qaWBTjwMI), you can check out these other resources:
+
+[The CodeRAD Developers Guide](https://shannah.github.io/CodeRAD/manual/)
+: This includes an introduction to the key concepts, as well as two tutorials:
+
+ ****1. [Getting Started](https://shannah.github.io/CodeRAD/manual/#getting-started)**** and [companion screencast](https://youtu.be/QdyO4tpYOHs).
+
+ ****2. [Building a Twitter Clone](https://shannah.github.io/CodeRAD/manual/#_app_example_1_a_twitter_clone)****
+
+****[The CodeRAD Wiki](https://github.com/shannah/CodeRAD/wiki)****
+: The best source for reference documentation on CodeRAD components.
+
+****[The CodeRAD Javadocs](https://shannah.github.io/CodeRAD/javadoc)****
+: JavaDocs for CodeRAD.
+
+****[Github Repository](https://github.com/shannah/CodeRAD)****
+: All the source for CodeRAD.
+
+## Sample Projects
+
+****1. [CodeRAD2 Samples](https://github.com/shannah/coderad2-samples)**** – Includes a growing set of samples demonstrating the use of CodeRAD components. **Views are located in the [src/main/rad/views directory](https://github.com/shannah/coderad2-samples/tree/master/common/src/main/rad/views/com/codename1/rad/sampler).**
+
+****2. [Tweet App](https://github.com/shannah/tweetapp)**** – A partial twitter clone.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/around.md b/docs/website/content/blog/around.md
new file mode 100644
index 0000000000..e70610337b
--- /dev/null
+++ b/docs/website/content/blog/around.md
@@ -0,0 +1,94 @@
+---
+title: Around
+slug: around
+url: /blog/around/
+original_url: https://www.codenameone.com/blog/around.html
+aliases:
+- /blog/around.html
+date: '2016-03-28'
+author: Shai Almog
+---
+
+
+
+Chen just released a [new cn1lib](https://github.com/chen-fishbein/CN1CircleProgress) for circular progress indicators
+of various types. This is an often requested feature and there were many ways to implement this in the past
+but it is now far easier to do this with shape clipping.
+
+You can use the circular progress API using code such as:
+
+
+ Form hi = new Form("Circle Progress");
+ hi.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
+ final CircleProgress p = new CircleProgress();
+ p.setProgress(100);
+ p.setClockwise(true);
+ p.setStartAngle(CircleProgress.START_9_OCLOCK);
+ hi.add(p);
+
+ final ArcProgress p2 = new ArcProgress();
+ p2.setProgress(70);
+ hi.add(p2);
+
+ final CircleFilledProgress p3 = new CircleFilledProgress();
+ p3.setProgress(70);
+ hi.add(p3);
+
+ Slider slider = new Slider();
+ slider.setEditable(true);
+ slider.addDataChangedListener(new DataChangedListener() {
+
+ @Override
+ public void dataChanged(int type, int index) {
+ p.setProgress(index);
+ p2.setProgress(index);
+ p3.setProgress(index);
+ }
+ });
+ hi.add(slider);
+
+ hi.show();
+
+Which results in this:
+
+
+
+Figure 1. Circle progress indicators in action
+
+### IntelliJ/IDEA Rewrite
+
+This has been a very slow news week due to many reasons but a big chunk of that is our focus on some big
+tasks.
+
+I’m working on a complete rewrite of the IntelliJ/IDEA plugin, I hope to have it out next week. This
+should bring IntelliJ/IDEA into par with the rest of the IDE’s and in my humble opinion it might leapfrog other IDE
+plugins.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Msizi** — March 31, 2016 at 5:06 pm ([permalink](https://www.codenameone.com/blog/around.html#comment-22728))
+
+> Great work!!!! just a question, how to change the color of the circle. instead of blue maybe use red or any different color
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faround.html)
+
+
+### **Chen Fishbein** — April 1, 2016 at 6:38 am ([permalink](https://www.codenameone.com/blog/around.html#comment-22460))
+
+> Chen Fishbein says:
+>
+> Thanks, the colors are coming from the “Slider” theme entry, just modify the Slider colors on the theme to change the colors
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Faround.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.md b/docs/website/content/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.md
new file mode 100644
index 0000000000..2080a6ffa2
--- /dev/null
+++ b/docs/website/content/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.md
@@ -0,0 +1,319 @@
+---
+title: Associating Your App with File Extension/Mime Type on iPhone (iOS), Android
+ & Windows
+slug: associating-your-app-with-file-extension-mime-types-iphone-android-windows
+url: /blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows/
+original_url: https://www.codenameone.com/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.html
+aliases:
+- /blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.html
+date: '2016-11-07'
+author: Steve Hannah
+---
+
+
+
+One of the compelling reasons to go native (vs say a web app) is to better integrate with the platform. One form of integration that is frequently handy is the ability register your app to handle certain file types so that it is listed as one of the options when a user tries to view a file of that type. Codename One supports this use case via the “AppArg” display property – the same, simple mechanism used for handling custom link types in your app.
+
+With the “Meme Maker” demo that I just created, I wanted users to be able to select a photo from another app (like Photos on Android), and send it directly to the Meme Maker app as the basis for creating a Meme.
+
+In case you’re not familiar with “Memes”, they are those sometimes annoying photos that litter your facebook feed with witty captions laid over them. E.g:
+
+
+
+Figure 1. Example cat meme
+
+Meme Maker is a very simple app. It allows the user to select a photo, and it provides some text laid over the photo which the user can edit. When the meme is finished, it can be exported as an image, and/or shared to Facebook or other social media.
+
+All this is simple to do in Codename One. Selecting an image, can be achieved using `Display.openGallery()`, which allows the user to choose from one of the images in their device’s photos. Sharing an image can be achieved via `Display.execute()` or with the `ShareButton` component.
+
+I wanted to go a step further, though, so that users could launch MemeMaker directly from their Photos app. For a simple app like this, allowing the user to “Share” an image **to** the app can significantly improve the user experience.
+
+### How to Register an App to open a File Type
+
+Registering your app to open a a file type involves two parts:
+
+ 1. Add some build hints to inject the appropriate metadata into each native platform config files (e.g. The info.plist on iOS, the manifest file on Android, etc..) to inform the native platform that the app can open the specified file types.
+
+ 2. Check for `Display.getInstance().getProperty("AppArg", null)` at the beginning of your app’s `start()` method to see if the app was opened as a result of file being opened or shared. If present, it will be the path to a file that you can access using `FileSystemStorage`.
+
+### An example from the “Meme Maker” demo
+
+Lets’ start by looking at the code that handles the “AppArg”. At the beginning of the `start()` method we have:
+
+
+ Display disp = Display.getInstance();
+ String arg = disp.getProperty("AppArg", null);
+ if (arg != null) {
+ disp.setProperty("AppArg", null);
+ disp.callSerially(()->{
+ fireImageSelected(arg);
+ });
+ }
+
+So, what we’ve done here is
+
+ 1. Check the “AppArg” property.
+
+ 1. If it is not null, we set it null (just so we don’t mistake it being set in future starts).
+
+ 2. I use `callSerially()` to defer the actual selection of the image until after the rest of the `start()` method has run. That is app-specific, and not necessary in general for processing app arguments.
+
+That’s all there is to it.
+
+Now the app is equipped to “handle” files that are passed to it on startup. However we still need to register the app with each platform so that the operating system knows to make our app available as a share target (or an “open with” target).
+
+### Android-Specific Configuration
+
+There are two build-hints related to Android that we will need to employ:
+
+ 1. `android.activity.launchMode=singleTask`.
+
+The default launch mode for Codename One apps is “singleTop”. Unfortunately this doesn’t really work very well if the app can be launched from other apps to open files. I won’t go into specifics here about the differences between “singleTop” and “singleTask” launch mode. Just know that if you want your app to work properly as a share target, you need to set this build hint to “singleTask”.
+
+You can read more about Android’s `activity:launchMode` directive [here](https://developer.android.com/guide/topics/manifest/activity-element.html).
+
+ 2. `android.xintent_filter`
+
+This is where we add the `` tags to be injected into the app’s manifest file. These filters will register our app to open specific file types. The value I used for **Meme Maker** is:
+
+
+
+
+
+
+
+
+
+
+
+
+The first filter says that the app is an eligible “share” target for files with mimetype “image/**“. The second says that the app is eligible to “Open” files with mimetype “image/** “. Each is used in different instances. A simple way to think of this is, from a Codename One’s app perspective:
+
+ 1. `Display.execute(filepath)` – will allow the user to “open” the file using apps that have registered an appropriate intent filter with action `android.intent.action.VIEW`.
+
+ 2. `Display.share(null, filepath, "image/png")` – will allow the user to “send” the file to an app that has registered an appropriate intent filter with action `android.intent.action.SEND`.
+
+Here are some sceen-shots of how the integration looks on my Nexus 5.
+
+I did a search on Google for “blank meme photos”. Once I found a photo, I did a long-press on the image (in Chrome), to open the context menu:
+
+
+
+Figure 2. Android chrome context menu
+
+Then when I tap on “Share”, it gives me a list of the apps that I can share this image to. MemeMaker is listed there:
+
+
+
+Figure 3. Share image to meme maker
+
+Then it opens Meme Maker with the image already loaded into the background:
+
+
+
+Figure 4. Meme maker with preloaded image
+
+### iOS-Specific Configuration
+
+On iOS, we only need concern ourselves with one build hint:
+
+`ios.plistInject`
+
+The content I used for the Meme Maker app is:
+
+
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeName
+ image
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Alternate
+ LSItemContentTypes
+
+ public.image
+
+
+
+
+Don’t be intimidated by this snippet. There’s a lot there, but for the most part it is just boiler-plate copy and paste. Here is a break-down of the values and their meaning:
+
+ 1. `CFBundleTypeName` – A name for this bundle type. You can provide pretty much any value you want here. I used “image”, but it could have been “foo” or “bar”.
+
+ 2. `CFBundleTypeRole` – The role of this app. In our case I’m just registering it as an image viewer. The value can be Editor, Viewer, Shell, or None. This key is required.
+
+ 3. `LSHandlerRank` – How iOS ranks the relevance against other apps that open this file type. Possible values: “Owner”, “Alternate”, “Default”, “None”
+
+ 4. `LSItemContentTypes` – A list of the content types that are being registered to be opened by the app. iOS uses UTIs instead of mimetypes here. The `public.image` UTI is basically the same as the `image/*` mimetype. You can see a list of all public UTIs [here](https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html).
+
+__ | iOS has (at least) two different mechanisms for handling file types in your app. The above `ios.plistInject` value will register the app to be able to “Open” an image file, but it won’t allow it to receive it as a share target. The distinction is subtle and it depends on what mechanism is used to launch the “Open with” or “Share” dialog in the source app. E.g. If you view a PDF inside Safari, it will provide an “Open with…” (label changed to “More…” in iOS 10) link in the top left, which, if tapped, will provide the user with a list of registered apps that can open a PDF. If our app was registered to open a PDF in the same way that it is registered to open images, then our app would appear in this list of elligible apps.
+---|---
+
+However, there is also a “Share” button at the bottom of the screen in Safari. This won’t include our app as it uses a different mechanism for registering apps. Registration to appear in this menu is more complicated and beyond the scope of this post.
+
+Unfortunately I couldn’t find an example in the latest OS where an app provides “Open with” an image. It seems that things are shifting towards “sharing” when images are involved, and as I mentioned above, this is a little more complex. However, for other files types, like PDF, the “open with” workflow is still common. For example, here is a sample of a PDF as viewed in iOS’ Safari. If I tap on the PDF, it provides a little menu along the top with a “More…” option, as shown here:
+
+
+
+And when I tap on “More…” I see:
+
+
+
+Figure 5. iOS Open with dialog
+
+The first application listed here is “OCR.net”, which is an app that I developed using Codename One. It includes the following `ios.plistInject` directive to be shown here:
+
+
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeName
+ pdf
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Alternate
+ LSItemContentTypes
+
+ com.adobe.pdf
+
+
+
+ CFBundleTypeName
+ image
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Alternate
+ LSItemContentTypes
+
+ public.image
+
+
+
+
+### Windows-Specific Configuration
+
+The process for UWP is similar to both iOS and Android. In this case we use the `windows.extensions` directive to inject content into the windows manifest file. In this case, we use:
+
+
+
+
+ imagesicon.png
+
+ .jpg
+ .jpeg
+ .gif
+ .png
+
+
+
+
+With this build hint, our app is registered to open files with .jpg, jpeg, .gif, and .png files. On the desktop, this means you can right click on files of these types, select “Open with” in the contextual menu, and then select “Meme maker” as shown here:
+
+
+
+For more information about the available options in UWP, see [handling file activation](https://msdn.microsoft.com/en-us/windows/uwp/launch-resume/handle-file-activation) on MSDN.
+
+#### Windows 10 Share Targets
+
+As with iOS and Android, Windows 10 treats “share targets” slightly differently than file associations. The `FileTypeAssociation` tag registers the app to be able to “open” files of the specified types, but it doesn’t register to be a share target. Share targets are those apps that appear in the sharing dialog when a users chooses “Share” from a context menu. E.g. When I right click on this image in Edge, it gives me an option to “Share” the image:
+
+
+
+On the desktop, this will open a sidebar with a list of applications to which this image can be shared:
+
+
+
+In the above screenshot, notice that Meme Maker is listed as one of the apps. This version of MemeMaker was built using an some additional options in the `windows.extensions` build hint:
+
+
+
+
+
+ .jpg
+ .gif
+ .png
+ .jpeg
+
+ StorageItems
+
+
+
+__ | For more information about the “windows.shareTarget” category, see [Microsoft’s docs](https://msdn.microsoft.com/en-us/library/windows/apps/br211466.aspx) on the subject.
+---|---
+
+With this share target information, the app was listed in the Sharing sidebar when an image file was shared by another app. Selecting “Meme maker” in this sidebar would open Mememaker inside the sharing sidebar as shown here:
+
+
+
+Figure 6. Meme maker loaded inside Windows 10 sidebar
+
+__ | Ultimately I opted not to include the “shareTarget” functionality in the finished app because it resulted in some peculiar behaviour when the app was opened in both the sharing sidebar and as a stand-alone app.
+---|---
+
+### Get the Meme Maker App
+
+ 1. [On the Play Store](https://play.google.com/store/apps/details?id=com.codename1.demos.mememaker)
+
+ 2. [In the Windows Store](https://www.microsoft.com/en-us/store/p/codename-one-meme-maker/9nblggh441nf)
+
+ 3. [In the iTunes Store](https://itunes.apple.com/us/app/codename-one-meme-maker/id1171538632)
+
+ 4. [On GitHub](https://github.com/shannah/mememaker)
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Carlos** — November 8, 2016 at 4:03 pm ([permalink](https://www.codenameone.com/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.html#comment-23053))
+
+> Excelent.
+>
+> One big step forward would be to read Exif rotation and correct the image accordingly, as this is something that happens very often. Most devices don’t actually rotate pictures, but mark them as such in the exif data.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fassociating-your-app-with-file-extension-mime-types-iphone-android-windows.html)
+
+
+### **bryan** — November 8, 2016 at 7:37 pm ([permalink](https://www.codenameone.com/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.html#comment-22806))
+
+> Great tutorial Steve – thanks.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fassociating-your-app-with-file-extension-mime-types-iphone-android-windows.html)
+
+
+### **Shai Almog** — November 9, 2016 at 7:31 am ([permalink](https://www.codenameone.com/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.html#comment-23043))
+
+> Thanks.
+>
+> AFAIK we already do that implicitly in our capture implementation.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fassociating-your-app-with-file-extension-mime-types-iphone-android-windows.html)
+
+
+### **Carlos** — November 9, 2016 at 9:09 am ([permalink](https://www.codenameone.com/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.html#comment-23085))
+
+> This is what I get in what should be a vertical pic…
+>
+> [https://uploads.disquscdn.c…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fassociating-your-app-with-file-extension-mime-types-iphone-android-windows.html)
+
+
+### **Shai Almog** — November 10, 2016 at 4:57 am ([permalink](https://www.codenameone.com/blog/associating-your-app-with-file-extension-mime-types-iphone-android-windows.html#comment-22989))
+
+> Looking at the code it seems to no longer be there, not sure why. I’ll have to ask on that.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fassociating-your-app-with-file-extension-mime-types-iphone-android-windows.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/async-debugging-with-intellij-idea.md b/docs/website/content/blog/async-debugging-with-intellij-idea.md
new file mode 100644
index 0000000000..5cf7a2302d
--- /dev/null
+++ b/docs/website/content/blog/async-debugging-with-intellij-idea.md
@@ -0,0 +1,152 @@
+---
+title: Async Debugging with IntelliJ IDEA
+slug: async-debugging-with-intellij-idea
+url: /blog/async-debugging-with-intellij-idea/
+original_url: https://www.codenameone.com/blog/async-debugging-with-intellij-idea.html
+aliases:
+- /blog/async-debugging-with-intellij-idea.html
+date: '2022-04-11'
+author: Steve Hannah
+description: We have added support for IntelliJ’s asynchronous code debugging feature,
+ so that you can more easily debug your asynchronous code.
+---
+
+We have added support for IntelliJ’s asynchronous code debugging feature, so that you can more easily debug your asynchronous code.
+
+
+
+When debugging your apps in IntelliJ, stack-traces will include the “async” context’s stack frames so that you can see the stack trace of the code that scheduled your asynchronous code. For example, methods like `callSerially()` are notoriously prickly to debug because the “logical” stack trace includes the stack frame in which `callSerially(Runnable)` is called, and also the frame in which the **Runnable**‘s `run()` method is called. It is very difficult to walk up this “logical” stack from a break-point inside the `run()` method.
+
+To demonstrate this point, consider the following code:
+
+```java
+
+ package com.codenameone.devmode;
+
+import com.codename1.ui.Button;
+import com.codename1.ui.Form;
+import com.codename1.ui.events.ActionEvent;
+import com.codename1.ui.layouts.BorderLayout;
+
+import static com.codename1.ui.CN.callSerially;
+
+public class TestAsyncDebugForm extends Form {
+
+ public TestAsyncDebugForm() {
+ super(new BorderLayout(BorderLayout.CENTER_BEHAVIOR_CENTER_ABSOLUTE));
+ Button btn = new Button("Hello 1");
+ btn.addActionListener(this::button1Clicked);
+ Button btn2 = new Button("Hello 2");
+ btn2.addActionListener(this::button2Clicked);
+ add(BorderLayout.CENTER, btn);
+ add(BorderLayout.SOUTH, btn2);
+ }
+
+ private void button1Clicked(ActionEvent evt) {
+ callSerially(printHello);
+ }
+
+ private void button2Clicked(ActionEvent evt) {
+ callSerially(printHello);
+ }
+
+ Runnable printHello = () -> {
+ printHello();
+ };
+
+ private void printHello() {
+ System.out.println("Hello");
+ }
+}
+
+
+```
+
+This creates a form with two buttons: “Hello 1” and “Hello 2”. Clicking on either button will ultimately trigger an async call to the `printHello()` method, but they follow different code paths to get there. Clicking “Hello 1” triggers the `button1Clicked()` method, and clicking “Hello 2” triggers the `button2Clicked()` method – both triggering an async call to `printHello()`.
+
+Let’s set a break point inside the `printHello()` method:
+
+
+
+If we debug the app and press “Hello 1”, we will see a stack trace like the following:
+
+
+
+There is no way to tell from this stack trace which button was pressed to trigger it. If we walk up the stack we hit a dead end at **executeSerialCall()**. This is because the break-point occurs in the asynchronous callback of `callSerially()`, so the original stack frame for the call to `callSerially()` is already “gone” by the time we hit our break-point.
+
+Now, let’s try this again with async debugging enabled. The stack trace this time will look like:
+
+
+
+We can now trace this break-point back to “Button 1” definitively because it displays both the “execution” stack frame’s trace, and the scheduler’s stack frame’s trace.
+
+### Enabling Async Debugging
+
+Async debugging requires:
+
+
+1. That you are using IntelliJ IDEA
+
+
+2. That your project is using Maven
+
+
+3. That your `cn1.version` property is set to 7.0.65 or higher.
+
+
+4. That your project is configured to use the `com.codename1.annotations.Async` annotations for the async stack traces feature. All projects created using the [Codename One initializr](https://start.codenameone.com/) after April 18th will include this configuration “out of the box”, so it should “just work”.
+
+### Configuring the Async Annotations
+
+As mentioned above, new projects created with [Codename One initializr](https://start.codenameone.com/) after April 18th, should include async stack traces out of the box. If you have an existing project on which you want to enable async traces, you just need to tell IntelliJ to use the Codename One annotations for async stack traces. The easiest way is to simply copy the [debugger.xml](https://github.com/shannah/cn1-maven-archetypes/blob/master/cn1app-archetype/src/main/resources/archetype-resources/.idea/debugger.xml) file from the cn1app-archetype into the `.idea` directory of your project.
+
+## Place the following into the .idea/debugger.xml file of your project to enable async stack-traces.
+
+```xml
+
+ xml version="1.0" encoding="UTF-8"?
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Alternatively you can follow the IntelliJ documentation for configuring custom annotations [here](https://www.jetbrains.com/help/idea/debug-asynchronous-code.html#custom_async_annotations). You should add `com.codename1.annotations.Async.Schedule` to the list of Async Schedule annotations, and `com.codename1.annotations.Async.Execute` to the list of Async Execute annotations. The configuration dialog is shown below.
+
+
+
+Figure 1. The Async Annotations configuration dialog in IntelliJ.
+
+### For More Information
+
+For more information about IntelliJ’s asynchronous debugging feature, see [the IntelliJ documentation on the subject](https://www.jetbrains.com/help/idea/debug-asynchronous-code.html).
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved here for historical context. New discussion happens in the Discussion section below._
+
+
+### **Antonio Rios** — April 14, 2022 at 3:20 am ([permalink](https://www.codenameone.com/blog/async-debugging-with-intellij-idea.html#comment-24534))
+
+> Antonio Rios says:
+>
+> Wonderful new features! Great job guys! I’m literally impress every time I visit the blog and see some new cool feature added. Keep those cool useful features coming please.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fasync-debugging-with-intellij-idea.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/asynchronous-media.md b/docs/website/content/blog/asynchronous-media.md
new file mode 100644
index 0000000000..84c58e8d0f
--- /dev/null
+++ b/docs/website/content/blog/asynchronous-media.md
@@ -0,0 +1,52 @@
+---
+title: Asynchronous Media
+slug: asynchronous-media
+url: /blog/asynchronous-media/
+original_url: https://www.codenameone.com/blog/asynchronous-media.html
+aliases:
+- /blog/asynchronous-media.html
+date: '2019-05-22'
+author: Shai Almog
+---
+
+
+
+There are a lot of fixes and new features that I don’t get to cover enough as I’ve been busy on several fronts. One of the new features is support for asynchronous media API’s. These let us create a media object without waiting for it to complete. This is very useful if you have a complex UI and want to play a media file while doing other things.
+
+E.g. if you’re scrolling in a social network feed and want to play a media preview. You might create a media object but don’t want it to block the current call. You can do this using code such as:
+
+
+ AsyncResource async = Display.getInstance().createMediaAsync(URL_TO_MEDIA, isVideo, null);
+ async.ready(mediaInstance -> playMedia(mediaInstance));
+
+You will notice the usage of `AsyncResource` which is similar to a future or a promise in other platforms. It lets you monitor the status of an asynchronous approach. This block would execute quickly but the `playMedia` call would happen when loading is completed.
+
+### Rendering Hints
+
+One of the API’s I dislike in JavaSE is the `Graphics2D` rendering hints. It’s a bit opaque in the choices it exposes. I want fast and good looking graphics but the tradeoff isn’t always clear. How much would I “pay” for good looking in this case in terms of speed and visa versa.
+
+Now we also have one rendering hint in our graphics:
+
+
+ graphics.setRenderingHints(Graphics.RENDERING_HINT_FAST);
+
+I’m not too crazy about the name as it’s a bit misleading. I’m sure developers would just turn it on to make everything “go fast” then complain when it has no impact… It doesn’t do that.
+
+Only iOS uses this and even then only when rendering images. Since copying images to textures is expensive, we keep the last generated texture cached. This works well if we are always rendering the image at the same size. If we are constantly rendering the same image at different sizes, then we’ll constantly be invalidating the cache, this results in artifacts. This affected pinch zoom in the image viewer [causing it to be choppy](https://github.com/codenameone/CodenameOne/issues/2786) as it had to regenerate a texture for every change in size. When Fast rendering is enabled, we now only invalidate the texture cache if the image is larger or smaller than the existing texture by more than a factor of 2.
+
+This change also fixed a bug that caused images to be rendered as black if they are larger than the max OGL texture size. Now it will cap the size of the texture at the max OGL size, and it will use the GPU to scale the texture to the desired size.
+
+### Uppercase
+
+`TextArea` now supports a new `UPPERCASE` constraint which lets you request uppercase input. Generally it will just popup the keyboard with the capslock on. You can use it as such:
+
+
+ textFieldOrArea.setConstrainer(TextArea.UPPERCASE);
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/attachments-network-speed-and-more.md b/docs/website/content/blog/attachments-network-speed-and-more.md
new file mode 100644
index 0000000000..c0928bc1dd
--- /dev/null
+++ b/docs/website/content/blog/attachments-network-speed-and-more.md
@@ -0,0 +1,43 @@
+---
+title: Attachments, Network Speed and More
+slug: attachments-network-speed-and-more
+url: /blog/attachments-network-speed-and-more/
+original_url: https://www.codenameone.com/blog/attachments-network-speed-and-more.html
+aliases:
+- /blog/attachments-network-speed-and-more.html
+date: '2013-09-04'
+author: Shai Almog
+---
+
+
+
+
+
+
+
+
+
+
+
+
+Our email api only supported a single attachment until now. We just added an api that allows for multiple attachments which we will add in the next update. Notice that multiple attachments will only work on iOS/Android at the moment.
+
+
+
+Also in this update you would find a fix for the Twitter service (see the TwitterRESTService class) and some other capabilities such as support for network performance issues.
+
+
+
+The simulator now allows you to simulate a case of no network connectivity and slow connectivity. This simulation isn’t accurate but it should help in gauging the feel for such cases and debugging complex scenarios. To see this in action just select the Network section in the simulator menu and select the mode for the simulator to run in.
+
+* * *
+
+Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended…. If that is the case please let us know via the comments section below.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/automatic-build-hints-configuration.md b/docs/website/content/blog/automatic-build-hints-configuration.md
new file mode 100644
index 0000000000..86db44705a
--- /dev/null
+++ b/docs/website/content/blog/automatic-build-hints-configuration.md
@@ -0,0 +1,53 @@
+---
+title: Automatic Build Hints Configuration
+slug: automatic-build-hints-configuration
+url: /blog/automatic-build-hints-configuration/
+original_url: https://www.codenameone.com/blog/automatic-build-hints-configuration.html
+aliases:
+- /blog/automatic-build-hints-configuration.html
+date: '2017-01-17'
+author: Shai Almog
+---
+
+
+
+We try to make Codename One “seamless”, this expresses itself in many small details such as the automatic detection of permissions on Android etc. The build servers go a long way in setting up the environment as intuitive. But it’s not enough, build hints are often confusing and obscure. It’s just hard to abstract the mess that is native mobile OS’s and the odd policies from Apple/Google…
+
+E.g. a common problem developers face is location code that doesn’t work in iOS. This is due to the `ios.locationUsageDescription` build hint that’s required. The reason we added that build hint was a requirement by Apple to provide a description for every app that uses the location service.
+
+We could detect usage of the API in the servers and inject some random string into place and in fact that was what we were about to do with [issue 1415](https://github.com/codenameone/CodenameOne/issues/1415) but then it occurred to us that there is a much simpler way that will also provide far more power…
+
+We added two new API’s to `Display`:
+
+
+ /**
+ * Returns the build hints for the simulator, this will only work in the debug environment and it's
+ * designed to allow extensions/API's to verify user settings/build hints exist
+ * @return map of the build hints that isn't modified without the codename1.arg. prefix
+ */
+ public Map getProjectBuildHints() {}
+
+ /**
+ * Sets a build hint into the settings while overwriting any previous value. This will only work in the
+ * debug environment and it's designed to allow extensions/API's to verify user settings/build hints exist.
+ * Important: this will throw an exception outside of the simulator!
+ * @param key the build hint without the codename1.arg. prefix
+ * @param value the value for the hint
+ */
+ public void setProjectBuildHint(String key, String value) {}
+
+Both of these allow us to detect if a build hint is set and if not (or if set incorrectly) set its value…
+
+So now if you will use the location API from the simulator and you didn’t define `ios.locationUsageDescription` we will implicitly define a string there. The cool thing is that you will now see that string in your settings and you would be able to customize it easily.
+
+However, this gets way better than just that trivial example!
+
+The real value is for 3rd party libraries, e.g. Google Maps or Parse. They can inspect the build hints in the simulator and show an error in case of a misconfiguration. They can even show a setup UI. Demos that need special keys in place can force the developer to set them up properly before continuing. We plan to make extensive use of this feature moving forward.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/automatic-caching.md b/docs/website/content/blog/automatic-caching.md
new file mode 100644
index 0000000000..10239bd451
--- /dev/null
+++ b/docs/website/content/blog/automatic-caching.md
@@ -0,0 +1,79 @@
+---
+title: Automatic Caching
+slug: automatic-caching
+url: /blog/automatic-caching/
+original_url: https://www.codenameone.com/blog/automatic-caching.html
+aliases:
+- /blog/automatic-caching.html
+date: '2016-12-14'
+author: Shai Almog
+---
+
+
+
+Caching server data locally is a huge part of the advantage a native app has over a web app. Normally this is
+non-trivial as it requires a delicate balance especially if you want to test the server resource for changes.
+
+HTTP provides two ways to do that the [ETag](https://en.wikipedia.org/wiki/HTTP_ETag) and
+[Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified). While both are
+great they are non-trivial to use and by no definition seamless.
+
+We just added an experimental feature to connection request that allows you to set the caching mode to one of
+4 states either globally or per connection request:
+
+ * **OFF** is the default meaning no caching.
+
+ * **SMART** means all get requests are cached intelligently and caching is “mostly” seamless
+
+ * **MANUAL** means that the developer is responsible for the actual caching but the system will not do a request on a resource that’s already “fresh”
+
+ * **OFFLINE** will fetch data from the cache and wont try to go to the server. It will generate a 404 error if data isn’t available
+
+You can toggle these in the specific request by using `setCacheMode(CachingMode)` and set the global
+default using `setDefaultCacheMode(CachingMode)`.
+
+__ | Caching only applies to `GET` operations, it will not work for `POST` or other methods
+---|---
+
+There are several methods of interest to keep an eye for:
+
+
+ protected InputStream getCachedData() throws IOException;
+ protected void cacheUnmodified() throws IOException;
+ public void purgeCache();
+ public static void purgeCacheDirectory() throws IOException;
+
+### getCachedData()
+
+This returns the cached data. This is invoked to implement `readResponse(InputStream)` when running offline
+or when we detect that the local cache isn’t stale.
+
+The smart mode implements this properly and will fetch the right data. However, the manual mode doesn’t
+store the data and relies on you to do so. In that case you need to return the data you stored at this point and must
+implement this method for manual mode.
+
+### cacheUnmodified()
+
+This is a callback that’s invoked to indicate a cache hit, meaning that we already have the data.
+
+The default implementation still tries to call all the pieces for compatibility (e.g. `readResponse`).
+However, if this is unnecessary you can override that method with a custom implementation or even a blank
+implementation to block such a case.
+
+### purgeCache & purgeCacheDirectory
+
+These methods are pretty self explanatory. Notice one caveat though…
+
+When you download a file or a storage element we don’t cache them and rely on the file/storage element to
+be present and serve as “cache”. When purging we won’t delete a file or storage element you downloaded and
+thus these might remain.
+
+However, we do remove the `ETag` and `Last-Modified` data so the files might get refreshed the next time around.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/automatically-install-update-distribute-cn1libs-extensions.md b/docs/website/content/blog/automatically-install-update-distribute-cn1libs-extensions.md
new file mode 100644
index 0000000000..cf3f5dd433
--- /dev/null
+++ b/docs/website/content/blog/automatically-install-update-distribute-cn1libs-extensions.md
@@ -0,0 +1,220 @@
+---
+title: Automatically Install, Update & Distribute cn1libs (extensions)
+slug: automatically-install-update-distribute-cn1libs-extensions
+url: /blog/automatically-install-update-distribute-cn1libs-extensions/
+original_url: https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html
+aliases:
+- /blog/automatically-install-update-distribute-cn1libs-extensions.html
+date: '2016-06-07'
+author: Shai Almog
+---
+
+
+
+Managing your project dependencies and 3rd party extensions among the hard to navigate list of cn1libs has
+always been challenging. We are now tackling this problem in the new settings UI which is scheduled to launch
+for all IDE’s this Friday.
+
+To get started just open the new Codename One settings UI:
+
+
+
+Figure 1. Launching the new preferences UI
+
+__ | You need to use an up to date plugin from the June 10th release
+---|---
+
+Then open the extensions option:
+
+
+
+Figure 2. Extensions Option In the Settings
+
+Once you launch the extensions UI you should see this screen where you can download/search thru available
+Codename One extensions.
+
+
+
+Figure 3. The Extensions UI
+
+Once downloaded you will see a check mark next to the installed extensions.
+
+### Adding your Own
+
+The list of extensions is based on a [github project](https://github.com/codenameone/CodenameOneLibs) which
+you can fork to extend. You can update the version of cn1libs you make and also contribute. Notice that while
+all the current libraries in the list are open source this is by no means a requirement…
+
+We have quite a few cn1libs already from the community and we’d appreciate more of those to help the community
+at large.
+
+### What’s Next?
+
+We will probably refine this process as it matures e.g. add more tagging based UI and make an “uninstall”
+process as well…
+
+However, this depends a lot on your involvement & feedback so let us know what you think and take part in the
+project.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Chidiebere Okwudire** — June 8, 2016 at 2:50 pm ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-21512))
+
+> This sounds really cool! I’m definitely gonna try it out in the upcoming parse4cn1 update scheduled for later this month and give you feedback.
+>
+> By the way, I have an interesting situation and I’d like to know how best to handle it. The current (and most likely upcoming) version(s) of parse4cn1 ships in two flavors: One with push notification support and one without, the reason being that push notification requires some native sdks which conflicted with those in CN1 causing build failures (e.g., Facebook SDK).
+> How best can I handle this? Make two separate CN1libs (e.g. Parse4CN1.Push and Parse4CN1.NoPush)? Ideas are most welcome.
+>
+> Can’t wait to try this out 🙂
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Shai Almog** — June 9, 2016 at 3:50 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22892))
+
+> Thanks!
+>
+> We wanted the current version to be as simple as possible and the only complexity we really tried to solve was relatively simple dependency management. So I don’t see another way other than the one you suggested.
+>
+> FYI parse4cn1 is already in the current repository (we added most of our existing cn1libs section). At the moment we didn’t take that strategy and it’s listed as the standard cn1lib.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Chidiebere Okwudire** — June 9, 2016 at 7:47 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22696))
+
+> Yeah, I already peeped at the git repo. The version number is also incorrect but that’s no problem. I’ll fix it within the coming update hopefully next week. At the time, I’ll also split it up
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Shai Almog** — June 9, 2016 at 8:05 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22810))
+
+> Shai Almog says:
+>
+> Notice that this isn’t the “actual” version number. It’s the version in our repo which is an integer. We use this to determine if there is an update only and this isn’t displayed to the user… So the number is fine in that sense.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Chidiebere Okwudire** — June 17, 2016 at 8:30 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22817))
+
+> Chidiebere Okwudire says:
+>
+> Good point. By the way, do the IDEs automatically detect updates of the github repo is are the changes only available after the weekly cn1 updates?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Shai Almog** — June 17, 2016 at 11:51 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22709))
+
+> Shai Almog says:
+>
+> Neither. It’s a separate process where we manually deploy the changes to the [codenameone.com]() website. We try to be quick about it but there is also caching from CDN and it’s a manual thing.
+>
+> The logic is that we want the ability to migrate hosting. In the past we had an update center for NetBeans on Google code and it seems some people were still using it until now… In the future github might come down on partial binary hosting and we’d like such an eventuality to be seamless to our users.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Jérémy MARQUER** — August 9, 2016 at 10:15 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22970))
+
+> Jérémy MARQUER says:
+>
+> Hey. I cannot access to the new Preferences UI of CN1 with eclipse. My cn1 plugin version is “1.0.0.201608062027”. Thanks.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Shai Almog** — August 10, 2016 at 5:37 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22470))
+
+> Shai Almog says:
+>
+> Hi,
+> is this on a Mac or a PC?
+> Are you using JDK 8 to run Eclipse (you need to set it up in eclipse.ini)?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Jérémy MARQUER** — August 10, 2016 at 7:09 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22734))
+
+> Jérémy MARQUER says:
+>
+> On a PC. Yes sure, I launch Eclipse with this flag
+> “-vm
+> C:/Program Files/Java/jre1.8.0_77/bin/javaw.exe”
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Shai Almog** — August 11, 2016 at 4:41 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-21456))
+
+> Shai Almog says:
+>
+> Check that you have the GUIBuilder jar at c:myuserhomedir.codenameoneguibuilder_1.jar
+>
+> Assuming it’s there try running it from command line using java -jar c:myuserhomedir.codenameoneguibuilder_1.jar -settings path_to_project[codenameone_settings.proper…]() are there any errors printed to the console?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Jérémy MARQUER** — August 11, 2016 at 7:12 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22557))
+
+> Jérémy MARQUER says:
+>
+> As I expected, I obtain the old settings UI (not the latest I think) …
+> (and no errors printed)
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Shai Almog** — August 12, 2016 at 4:16 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22903))
+
+> Shai Almog says:
+>
+> That’s a problem. We’ll look into it.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Jérémy MARQUER** — August 17, 2016 at 4:31 pm ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-22635))
+
+> Jérémy MARQUER says:
+>
+> It’s ok, thanks.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Julien Sosin** — December 5, 2017 at 3:24 pm ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-23705))
+
+> Julien Sosin says:
+>
+> Hi !
+>
+> How can I delete a lib ? I tried CodeScanner but it looks deprecated and I can’t build iOS app anymore :/
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+
+### **Shai Almog** — December 6, 2017 at 9:11 am ([permalink](https://www.codenameone.com/blog/automatically-install-update-distribute-cn1libs-extensions.html#comment-23713))
+
+> Shai Almog says:
+>
+> Hi,
+> there is currently no standard uninstaller but it shouldn’t be too hard. See the instructions I posted here: [https://stackoverflow.com/a…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomatically-install-update-distribute-cn1libs-extensions.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/automating-releases.md b/docs/website/content/blog/automating-releases.md
new file mode 100644
index 0000000000..ee0198a8a7
--- /dev/null
+++ b/docs/website/content/blog/automating-releases.md
@@ -0,0 +1,122 @@
+---
+title: Automating Releases
+slug: automating-releases
+url: /blog/automating-releases/
+original_url: https://www.codenameone.com/blog/automating-releases.html
+aliases:
+- /blog/automating-releases.html
+date: '2015-05-31'
+author: Shai Almog
+---
+
+
+
+Our website deployment has become even more complex thanks to the [demos section](/demos.html).
+The crux of it is in updating the demos with every small update to the JavaScript build process which is why
+we implemented a build option based on the work we did for our [CI (Jenkins) integration](/blog/continuous-integration.html).
+This work essentially allows to build a Codename One app synchronously which is useful when you want
+to do things such as continuous integration or release engineering.
+Notice that the synchronous build feature is an enterprise only feature since its overuse can have a very heavy toll on our servers.
+
+Essentially we copied the existing build.xml to a separate file to prevent updates from overriding it. We then added
+targets such as this for the kitchen sink:
+
+
+
+
+
+
+This is effectively a copy and paste of the `build-for-javascript` target where we added the line
+`automated="true"` to indicate that this build works in a singular process.
+After the build completes we are left with a `result.zip` file in the `dist` folder. Which
+we unzip to find all the files from the build server:
+
+
+
+
+
+
+
+
+
+While the sample above shows the JavaScript build target it can be applied to any of the Codename One build
+targets and is remarkably useful for a release engineering process. When you need to release one version for all platforms
+on a frequent basis even a minute automation like this makes a big difference.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Blessing Mahlalela** — February 9, 2017 at 4:35 pm ([permalink](https://www.codenameone.com/blog/automating-releases.html#comment-23223))
+
+> Blessing Mahlalela says:
+>
+> Hi during development I noticed that if I try to send multiple builds ie Send Android, Send iOS.. on Netbeans etc. I would receive a compile error if I send them too quickly (simultaneously), I think CN1 deletes some files during the send build process. In any case the reason why I saying that is, I have Jenkins setup and would like to return a [result.zip]() containing multiple platform result files ie Android, iOS, Web, Desktop. How can I go about doing this on Jenkins build.xml? I am currently able to set ANT targets on Jenkins pre and post build.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomating-releases.html)
+
+
+### **Shai Almog** — February 9, 2017 at 6:32 pm ([permalink](https://www.codenameone.com/blog/automating-releases.html#comment-21563))
+
+> Shai Almog says:
+>
+> Yes, you can send concurrent builds but not at once. If you use automation to do this your user can’t send a build at that exact time. It’s just a limitation in the way the system was designed as the code that allocates a build needs to reserve a spot.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomating-releases.html)
+
+
+### **Blessing Mahlalela** — February 9, 2017 at 6:43 pm ([permalink](https://www.codenameone.com/blog/automating-releases.html#comment-23319))
+
+> Blessing Mahlalela says:
+>
+> Ok, I have now managed to call an ANT “build-for-javascript” target from the build xml. Thanks a lot for this, no more sitting and waiting for builds, secondly the automated test recorder will become a great resource to small dev organisations that just don’t have budget for dedicated test teams!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomating-releases.html)
+
+
+### **Blessing Mahlalela** — February 9, 2017 at 6:46 pm ([permalink](https://www.codenameone.com/blog/automating-releases.html#comment-23071))
+
+> Blessing Mahlalela says:
+>
+> One more question. How can I add multiple ANT arguments on Jenkins? I would like to automate the building of Android, iOS & web on every successful CI build.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomating-releases.html)
+
+
+### **Blessing Mahlalela** — February 9, 2017 at 8:21 pm ([permalink](https://www.codenameone.com/blog/automating-releases.html#comment-23237))
+
+> Blessing Mahlalela says:
+>
+> Managed to do multiple builds by adding additional ANT build steps. Had to configure signing certificates also.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomating-releases.html)
+
+
+### **Shai Almog** — February 9, 2017 at 8:49 pm ([permalink](https://www.codenameone.com/blog/automating-releases.html#comment-23077))
+
+> Shai Almog says:
+>
+> Yes we do them one by one since they are synchronous. I hope to write a more detailed blog on doing this in a future update just didn’t get around to doing it.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautomating-releases.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/autorenewing-subscriptions-in-ios-and-android.md b/docs/website/content/blog/autorenewing-subscriptions-in-ios-and-android.md
new file mode 100644
index 0000000000..328bba292a
--- /dev/null
+++ b/docs/website/content/blog/autorenewing-subscriptions-in-ios-and-android.md
@@ -0,0 +1,829 @@
+---
+title: Auto-Renewing Subscriptions in iOS and Android
+slug: autorenewing-subscriptions-in-ios-and-android
+url: /blog/autorenewing-subscriptions-in-ios-and-android/
+original_url: https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html
+aliases:
+- /blog/autorenewing-subscriptions-in-ios-and-android.html
+date: '2017-01-02'
+author: Steve Hannah
+---
+
+
+
+__ | This is the third post in a three-part series on In-App purchase. Please check out [Part I: Introduction to In-App Purchase](https://www.codenameone.com/blog/intro-to-in-app-purchase.html) and [Part 2: Implementing Non-Renewable Subscriptions](https://www.codenameone.com/blog/in-app-purchase-non-renewable-subscriptions.html).
+---|---
+
+Auto-renewable subscriptions provide, arguably, an easier path to recurring revenue than non-renewable subscriptions because all of the subscription stuff is handled by the app store. You defer almost entirely to the app store (iTunes for iOS, and Play for Android) for billing and management.
+
+If there is a down-side, it would be that you are also subject to the rules of each app store – and they take their cut of the revenue. On iOS, you keep 70% of the revenue for the first year of a subscription. This increases to 85% after the first year. Google also let’s you keep 70% of the revenue on subscriptions. I read a number of news articles from June 2016, stating that they [planned to increase this to 85%](http://www.androidauthority.com/devs-to-keep-85-percent-of-subscription-payments-697528/) to match Apple but I haven’t been able to find any corroborating information on the Play site itself, so at the time of writing, it appears that they are still on the 70/30 split model.
+
+ 1. For more information about Apple’s auto-renewable subscription features and rules see [this document](https://developer.apple.com/app-store/subscriptions/).
+
+ 2. For more information about subscriptions in Google play, see [this document](https://developer.android.com/google/play/billing/billing_subscriptions.html).
+
+## Auto-Renewable vs Non-Renewable. Best Choice?
+
+When deciding between auto-renewable and non-renewable subscriptions, as always, the answer will depend on your needs and preferences. Auto-renewables are nice because it takes the process completely out of your hands. You just get paid. On the other hand, there are valid reasons to want to use non-renewables. E.g. You can’t cancel an auto-renewable subscription for a user. They have to do that themselves. You may also want more control over the subscription and renewal process, in which case a non-renewable might make more sense.
+
+I recommend [this blog post](https://marco.org/2013/12/02/auto-renewable-subscriptions) for a well-informed, critical review of Apple’s auto-renew process. (TLDR> He says to **never** use auto-renewables). I don’t have as much experience with in-app purchase as that author, but from my experiments, the auto-renewable option seems like a perfectly good solution.
+
+## Learning By Example
+
+The remainder of this post describes the general workflow of subscription management on the server. It also demonstrates how use Apple’s and Google’s web services to validate receipts and stay informed of important events (such as when users cancel or renew their subscriptions).
+
+## Building the IAP Demo Project
+
+To aid in this process, I’ve created a fully-functional in-app purchase demo project that includes both a [client app](https://gist.github.com/shannah/b61b9b6b35ea0eac923a54163f5d4deb) and a [server app](https://github.com/shannah/cn1-iap-demo-server).
+
+### Setting up the Client Project
+
+ 1. Create a new Codename One project in Netbeans, and choose the “Bare-bones Hello World Template”. You should make your package name something unique so that you are able to create real corresponding apps in both Google Play and iTunes connect.
+
+ 2. Once the project is created, copy [this source file](https://gist.github.com/shannah/b61b9b6b35ea0eac923a54163f5d4deb) contents into your main class file. Then change the package name, and class name in the file to match your project settings. E.g. change `package ca.weblite.iapdemo;` to `package ;` and `class IAPDemo implements PurchaseCallback` to `class YourClassName implements PurchaseCallback`.
+
+ 3. Add the [Generic Web Service Client](https://github.com/shannah/cn1-generic-webservice-client) library to your project by going to “Codename Settings” > “Extensions”, finding that library, and click “Download”. Then “Refresh CN1 libs” as it suggests.
+
+ 4. Change the `localHost` property to point to your local machine’s network address. Using “http://localhost” is not going to cut it here because when the app is running on a phone, it needs to be able to connect to your web server over the network. This address will be your local network address (e.g. 192.168.0.9, or something like that).
+
+ private static final String localHost = "http://10.0.1.32";
+
+ 5. Add the `ios.plistInject` build hint to your project with the value “NSAppTransportSecurityNSAllowsArbitraryLoads”. This is so that we can use http urls in iOS. Since we don’t intend to full publish this app, we can cut corners like this. If you were creating a real app, you would use proper secure URLs.
+
+__ | In the client project, you’ll notice some places where we use `Purchase.purchase(sku)` for purchasing a product, and other places where we use `Purchase.subscribe(sku)`. The correct method will depend on how you have set up the product in the Google Play store. If the product is set up as a subscription, you **must** use `subscribe()`. Otherwise, you should use `purchase()`.
+---|---
+
+### Setting up the Server Project
+
+Download the CN1-IAP-Server demo project from Github, and run its “install-deps” ANT task in order to download and install its dependencies to your local Maven repo.
+
+__ | For the following commands to work, make sure you have “ant”, “mvn”, and “git” in your environment PATH.
+---|---
+
+
+ $ git clone https://github.com/shannah/cn1-iap-demo-server
+ $ cd cn1-iap-demo-server
+ $ ant install-deps
+
+Open the project in Netbeans
+
+### Setting up the Database
+
+ 1. Create a new database in your preferred DBMS. Call it anything you like.
+
+ 2. Create a new table named “RECEIPTS” in this database with the following structure:
+
+ create TABLE RECEIPTS
+ (
+ TRANSACTION_ID VARCHAR(128) not null,
+ USERNAME VARCHAR(64) not null,
+ SKU VARCHAR(128) not null,
+ ORDER_DATA VARCHAR(32000),
+ PURCHASE_DATE BIGINT,
+ EXPIRY_DATE BIGINT,
+ CANCELLATION_DATE BIGINT,
+ LAST_VALIDATED BIGINT,
+ STORE_CODE VARCHAR(20) default '' not null,
+ primary key (TRANSACTION_ID, STORE_CODE)
+ )
+
+ 3. Open the “persistence.xml” file in the server netbeans project.
+
+
+
+ 4. Change the data source to the database you just created.
+
+
+
+If you’re not sure how to create a data source, see my [previous tutorial on connecting to a MySQL database](https://www.codenameone.com/blog/connecting-to-a-mysql-database-part-2.html).
+
+### Testing the Project
+
+At this point we should be able to test out the project in the Codename One simulator to make sure it is working.
+
+ 1. Build and Run the server project in Netbeans. You may need to tell it which application server you wish to run it on. I am running it on the Glassfish 4.1 that comes bundled with Netbeans.
+
+ 2. Build and run the client project in Netbeans. This should open the Codename One simulator.
+
+When the app first opens you’ll see a screen as follows:
+
+
+
+This screen is for testing consumable products, so we won’t be making use of this right now.
+
+Open the hamburger menu and select “Subscriptions”. You should see something like this:
+
+
+
+Click on the “Subscribe 1 Month No Ads” button. You will be prompted to accept the purchase:
+
+
+
+Upon completion, the app will submit the purchase to your server, and if all went well, it will retrieve the updated list of receipts from your server also, and update the label on this form to say “No Ads. Expires ”:
+
+
+
+__ | This project is set up to use an expedited expiry date schedule for purchases from the simulator. 1 month = 5 minutes. 3 months = 15 minutes. This helps for testing. That is why your expiry date may be different than expected.
+---|---
+
+Just to verify that the receipt was inserted correctly, you should check the contents of your “RECEIPTS” table in your database. In Netbeans, I can do this easily from the “Services” pane. Expand the database connection down to the RECEIPTS table, right click “RECEIPTS” and select “View Data”. This will open a data table similar the the following:
+
+
+
+
+
+A few things to mention here:
+
+ 1. The “username” was provided by the client. It is hard-coded to “admin”, but the idea is that you would have the user log in and you would have access to their real username.
+
+ 2. All dates are stored as unix timestamps in milliseconds.
+
+If you delete the receipt from your database, then press the “Synchronize Receipts” button in your app, the app will again say “No subscriptions.” Similarly if you wait 5 minutes and hit “Synchronize receipts” the app will say no subscriptions found, and the “ads” will be back.
+
+#### Troubleshooting
+
+Let’s not pretend that everything worked for you on the first try. There’s a lot that could go wrong here. If you make a purchase and nothing appears to happen, the first thing you should do is check the Network Monitor in the simulator (“Simulate” > “Network” > “Network Monitor”). You should see a list of network requests. Some will be GET requests and there will be at least one POST request. Check the response of these requests to see if they succeeded.
+
+Also check the Glassfish server log to see if there is an exception.
+
+Common problems would be that the URL you have set in the client app for `endpointURL` is incorrect, or that there is a database connection problem.
+
+## Looking at the Source of the App
+
+Now that we’ve set up and built the app, let’s take a look at the source code so you can see how it all works.
+
+### Client Side
+
+I use the [Generic Webservice Client Library](https://github.com/shannah/cn1-generic-webservice-client) from inside my `ReceiptStore` implementation to load receipts from the web service, and insert new receipts to the database.
+
+The source for my ReceiptStore is as follows:
+
+
+ private ReceiptStore createReceiptStore() {
+ return new ReceiptStore() {
+
+ RESTfulWebServiceClient client = createRESTClient(receiptsEndpoint);
+
+ @Override
+ public void fetchReceipts(SuccessCallback callback) {
+ RESTfulWebServiceClient.Query query = new RESTfulWebServiceClient.Query() {
+
+ @Override
+ protected void setupConnectionRequest(RESTfulWebServiceClient client, ConnectionRequest req) {
+ super.setupConnectionRequest(client, req);
+ req.setUrl(receiptsEndpoint);
+ }
+
+ };
+ client.find(query, rowset->{
+ List out = new ArrayList();
+ for (Map m : rowset) {
+ Result res = Result.fromContent(m);
+ Receipt r = new Receipt();
+ r.setTransactionId(res.getAsString("transactionId"));
+ r.setPurchaseDate(new Date(res.getAsLong("purchaseDate")));
+ r.setQuantity(1);
+ r.setStoreCode(m.getAsString("storeCode"));
+ r.setSku(res.getAsString("sku"));
+
+ if (m.containsKey("cancellationDate") && m.get("cancellationDate") != null) {
+ r.setCancellationDate(new Date(res.getAsLong("cancellationDate")));
+ }
+ if (m.containsKey("expiryDate") && m.get("expiryDate") != null) {
+ r.setExpiryDate(new Date(res.getAsLong("expiryDate")));
+ }
+ out.add(r);
+
+ }
+ callback.onSucess(out.toArray(new Receipt[out.size()]));
+ });
+ }
+
+ @Override
+ public void submitReceipt(Receipt r, SuccessCallback callback) {
+ Map m = new HashMap();
+ m.put("transactionId", r.getTransactionId());
+ m.put("sku", r.getSku());
+ m.put("purchaseDate", r.getPurchaseDate().getTime());
+ m.put("orderData", r.getOrderData());
+ m.put("storeCode", r.getStoreCode());
+ client.create(m, callback);
+ }
+
+ };
+ }
+
+Notice that we are not doing any calculation of expiry dates in our client app, as we did in the previous post (on non-renewable receipts). Since we are using a server now, it makes sense to move all of that logic over to the server.
+
+The `createRESTClient()` method shown there simply creates a `RESTfulWebServiceClient` and configuring it to use basic authentication with a username and password. The idea is that your user would have logged into your app at some point, and you would have a username and password on hand to pass back to the web service with the receipt data so that you can connect the subscription to a user account. The source of that method is listed here:
+
+
+ /**
+ * Creates a REST client to connect to a particular endpoint. The REST client
+ * generated here will automatically add the Authorization header
+ * which tells the service what platform we are on.
+ * @param url The url of the endpoint.
+ * @return
+ */
+ private RESTfulWebServiceClient createRESTClient(String url) {
+ return new RESTfulWebServiceClient(url) {
+
+ @Override
+ protected void setupConnectionRequest(ConnectionRequest req) {
+ try {
+ req.addRequestHeader("Authorization", "Basic " + Base64.encode((getUsername()+":"+getPassword()).getBytes("UTF-8")));
+ } catch (Exception ex) {}
+ }
+
+ };
+ }
+
+### Server-Side
+
+On the server-side, our REST controller is a standard JAX-RS REST interface. I used Netbeans web service wizard to generate it and then modified it to suit my purposes. The methods of the `ReceiptsFacadeREST` class pertaining to the REST API are shown here:
+
+
+ @Stateless
+ @Path("com.codename1.demos.iapserver.receipts")
+ public class ReceiptsFacadeREST extends AbstractFacade {
+
+ // ...
+
+ @POST
+ @Consumes({"application/xml", "application/json"})
+ public void create(Receipts entity) {
+
+ String username = credentialsWithBasicAuthentication(request).getName();
+ entity.setUsername(username);
+
+ // Save the receipt first in case something goes wrong in the validation stage
+ super.create(entity);
+
+ // Let's validate the receipt
+ validateAndSaveReceipt(entity);
+ // validates the receipt against appropriate web service
+ // and updates database if expiry date has changed.
+ }
+
+ // ...
+ @GET
+ @Override
+ @Produces({"application/xml", "application/json"})
+ public List findAll() {
+ String username = credentialsWithBasicAuthentication(request).getName();
+ return getEntityManager()
+ .createNamedQuery("Receipts.findByUsername")
+ .setParameter("username", username)
+ .getResultList();
+ }
+
+The magic happens inside that `validateAndSaveReceipt()` method, which I’ll cover in detail later on in this post.
+
+#### Notifications
+
+It is important to note that you will not be notified by apple or google when changes are made to subscriptions. It is up to you to periodically “poll” their web service to find if any changes have been made. Changes we would be interested in are primarily renewals and cancellations. In order to deal with this, set up a method to run periodically (once-per day might be enough). For testing, I actually set it up to run once per minute as shown below:
+
+
+ private static final long ONE_DAY = 24 * 60 * 60 * 1000;
+ private static final long ONE_DAY_SANDBOX = 10 * 1000;
+ @Schedule(hour="*", minute="*")
+ public void validateSubscriptionsCron() {
+ System.out.println("----------- DOING TIMED TASK ---------");
+ List res = null;
+ final Set completedTransactionIds = new HashSet();
+ for (String storeCode : new String[]{Receipt.STORE_CODE_ITUNES, Receipt.STORE_CODE_PLAY}) {
+ while (!(res = getEntityManager().createNamedQuery("Receipts.findNextToValidate")
+ .setParameter("threshold", System.currentTimeMillis() - ONE_DAY_SANDBOX)
+ .setParameter("storeCode", storeCode)
+ .setMaxResults(1)
+ .getResultList()).isEmpty() &&
+ !completedTransactionIds.contains(res.get(0).getTransactionId())) {
+
+ final Receipts curr = res.get(0);
+ completedTransactionIds.add(curr.getTransactionId());
+ Receipts[] validatedReceipts = validateAndSaveReceipt(curr);
+ em.flush();
+ for (Receipts r : validatedReceipts) {
+ completedTransactionIds.add(r.getTransactionId());
+ }
+
+ }
+ }
+ }
+
+That method simply finds all of the receipts in the database that haven’t been validated in some period of time, and validates it. Again, the magic happens inside the `validateAndSaveReceipt()` method which we cover later.
+
+__ | In this example we only validate receipts from the iTunes and Play stores because those are the only ones that we currently support auto-renewing subscriptions on.
+---|---
+
+## The CN1-IAP-Validator Library
+
+For the purpose of this tutorial, I created a library to handle receipt validation in a way that hides as much of the complexity as possible. It supports both Google Play receipts and iTunes receipts.
+
+The general usage is as follows:
+
+
+ IAPValidator validator = IAPValidator.getValidatorForPlatform(receipt.getStoreCode());
+ if (validator == null) {
+ // no validators were found for this store
+ // Do custom validation
+ } else {
+ validator.setAppleSecret(APPLE_SECRET);
+ validator.setGoogleClientId(GOOGLE_DEVELOPER_API_CLIENT_ID);
+ validator.setGooglePrivateKey(GOOGLE_DEVELOPER_PRIVATE_KEY);
+ Receipt[] result = validator.validate(receipt);
+ ...
+ }
+
+As you can see from this snippet, the complexity of receipt validation has been reduced to entering three configuration strings:
+
+ 1. `APPLE_SECRET` – This is a “secret” string that you will get from iTunes connect when you set up your in-app products.
+
+ 2. `GOOGLE_DEVELOPER_API_CLIENT_ID` – A client ID that you’ll get from the google developer API console when you set up your API service credentials.
+
+ 3. `GOOGLE_DEVELOPER_PRIVATE_KEY` – A PKCS8 encoded string with an RSA private key that you’ll receive at the same time as the `GOOGLE_DEVELOPER_API_CLIENT_ID`.
+
+I will go through the steps to obtain these values later on in this post.
+
+## The `validateAndSaveReceipt()` Method
+
+You are now ready to see the full magic of the `validateAndSaveReceipt()` method in all its glory:
+
+
+ /**
+ * Validates a given receipt, updating the expiry date,
+ * @param receipt The receipt to be validated
+ * @param forInsert If true, then an expiry date will be calculated even if there is no validator.
+ */
+ private Receipts[] validateAndSaveReceipt(Receipts receipt) {
+ EntityManager em = getEntityManager();
+ Receipts managedReceipt = getManagedReceipt(receipt);
+ // managedReceipt == receipt if receipt is in database or null otherwise
+
+ if (Receipt.STORE_CODE_SIMULATOR.equals(receipt.getStoreCode())) { __**(1)**
+ if (receipt.getExpiryDate() == null && managedReceipt == null) {
+ //Not inserted yet and no expiry date set yet
+ Date dt = calculateExpiryDate(receipt.getSku(), true);
+ if (dt != null) {
+ receipt.setExpiryDate(dt.getTime());
+ }
+ }
+ if (managedReceipt == null) {
+ // Receipt is not in the database yet. Add it
+ em.persist(receipt);
+ return new Receipts[]{receipt};
+ } else {
+ // The receipt is already in the database. Update it.
+ em.merge(managedReceipt);
+ return new Receipts[]{managedReceipt};
+ }
+ } else {
+ // It is not a simulator receipt
+ IAPValidator validator = IAPValidator.getValidatorForPlatform(receipt.getStoreCode());
+ if (validator == null) {
+ // Receipt must have come from a platform other than iTunes or Play
+ // Because there is no validator
+
+ if (receipt.getExpiryDate() == null && managedReceipt == null) {
+ // No expiry date.
+ // Generate one.
+ Date dt = calculateExpiryDate(receipt.getSku(), false);
+ if (dt != null) {
+ receipt.setExpiryDate(dt.getTime());
+ }
+
+ }
+ if (managedReceipt == null) {
+ em.persist(receipt);
+ return new Receipts[]{receipt};
+ } else {
+ em.merge(managedReceipt);
+ return new Receipts[]{managedReceipt};
+ }
+
+ }
+
+ // Set credentials for the validator
+ validator.setAppleSecret(APPLE_SECRET);
+ validator.setGoogleClientId(GOOGLE_DEVELOPER_API_CLIENT_ID);
+ validator.setGooglePrivateKey(GOOGLE_DEVELOPER_PRIVATE_KEY);
+
+ // Create a dummy receipt with only transaction ID and order data to pass
+ // to the validator. Really all it needs is order data to be able to validate
+ Receipt r2 = Receipt();
+ r2.setTransactionId(receipt.getTransactionId());
+ r2.setOrderData(receipt.getOrderData());
+ try {
+ Receipt[] result = validator.validate(r2);
+ // Depending on the platform, result may contain many receipts or a single receipt
+ // matching our receipt. In the case of iTunes, none of the receipt transaction IDs
+ // might match the original receipt's transactionId because the validator
+ // will set the transaction ID to the *original* receipt's transaction ID.
+ // If none match, then we should remove our receipt, and update each of the returned
+ // receipts in the database.
+ Receipt matchingValidatedReceipt = null;
+ for (Receipt r3 : result) {
+ if (r3.getTransactionId().equals(receipt.getTransactionId())) {
+ matchingValidatedReceipt = r3;
+ break;
+ }
+ }
+
+ if (matchingValidatedReceipt == null) {
+ // Since the validator didn't find our receipt,
+ // we should remove the receipt. The equivalent
+ // is stored under the original receipt's transaction ID
+ if (managedReceipt != null) {
+ em.remove(managedReceipt);
+ managedReceipt = null;
+ }
+ }
+ List out = new ArrayList();
+ // Now go through and
+ for (Receipt r3 : result) {
+ if (r3.getOrderData() == null) {
+ // No order data found in receipt. Setting it to the original order data
+ r3.setOrderData(receipt.getOrderData());
+ }
+ Receipts eReceipt = new Receipts();
+ eReceipt.setTransactionId(r3.getTransactionId());
+ eReceipt.setStoreCode(receipt.getStoreCode());
+ Receipts eManagedReceipt = getManagedReceipt(eReceipt);
+ if (eManagedReceipt == null) {
+ copy(eReceipt, r3);
+ eReceipt.setUsername(receipt.getUsername());
+ eReceipt.setLastValidated(System.currentTimeMillis());
+ em.persist(eReceipt);
+ out.add(eReceipt);
+ } else {
+
+ copy(eManagedReceipt, r3);
+ eManagedReceipt.setUsername(receipt.getUsername());
+ eManagedReceipt.setLastValidated(System.currentTimeMillis());
+ em.merge(eManagedReceipt);
+ out.add(eManagedReceipt);
+ }
+ }
+
+ return out.toArray(new Receipts[out.size()]);
+
+ } catch (Exception ex) {
+ // We should probably store some info about the failure in the
+ // database to make it easier to find receipts that aren't validating,
+ // but for now we'll just log it.
+ Log.p("Failed to validate receipt "+r2);
+ Log.p("Reason: "+ex.getMessage());
+ Log.e(ex);
+ return new Receipts[]{receipt};
+
+ }
+ }
+ }
+
+__**1** | We need to handle the case where the app is being used in the CN1 simulator. We’ll treat this
+as a non-renewable receipt, and we’ll calculate the expiry date using an “accelerated” clock to assist in testing.
+---|---
+
+__ | In many of the code snippets for the Server-side code, you’ll see references to both a `Receipts` class and a `Receipt` class. I know this is slightly confusing. The `Receipts` class is a JPA entity the encapsulates a row from the “receipts” table of our SQL database. The `Receipt` class is `com.codename1.payment.Receipt`. It is used to interface with the IAP validation library.
+---|---
+
+## Google Play Setup
+
+### Creating the App in Google Play
+
+In order to test out in-app purchase on an Android device, you’ll need to create an app the [Google Play Developer Console](https://play.google.com/apps/publish/). I won’t describe the process in this post, but there is plenty of information around the internet on how to do this. Some useful references for this include:
+
+ 1. [Getting Started With Publishing](https://developer.android.com/distribute/googleplay/start.html) – If you don’t already have an account with Google to publish your apps.
+
+ 2. [Launch Checklist](https://developer.android.com/distribute/tools/launch-checklist.html)
+
+#### Graphics, Icons, etc..
+
+You are required to upload some screenshots and feature graphics. Don’t waste time making these perfect. For the screenshots, you can just use the “Screenshot” option in the simulator. (Use the Nexus 5 skin). For the feature graphics, I used [this site](https://www.norio.be/android-feature-graphic-generator/) that will generate the graphics in the correct dimensions for Google Play. You can also just leave the icon as the default Codename One icon.
+
+#### Creating Test Accounts
+
+__ | You cannot purchase in-app products from your app using your publisher account. You need to set up at least one test account for the purpose of testing the app.
+---|---
+
+In order to test your app, you need to set up a test account. A test account must be associated with a real gmail email address. If you have a domain that is managed by Google apps, then you can also use an address from that domain.
+
+The full process for testing in-app billing can be found in [this google document](https://developer.android.com/google/play/billing/billing_testing.html). However, I personally found this documentation difficult to follow.
+
+For your purposes, you’ll need to set up a tester list in Google Play. Choose “Settings” > “Tester Lists”. Then create a list with all of the email address that you want to have treated as test accounts. Any purchases made by these email addresses will be treated as “Sandbox” purchases, and won’t require real money to change hands.
+
+#### Alpha Channel Distribution
+
+In order to test in-app purchase on Android, you **must** first publish your app. You can’t just build and install your app manually. The app needs to be published on the Play store, and it must be installed **through** the play store for in-app purchase to work. Luckily you can publish to an Alpha channel so that your app won’t be publicly available.
+
+For more information about setting up alpha testing on Google play see [this Google support document on the subject](https://support.google.com/googleplay/android-developer/answer/3131213?hl=en).
+
+Once you have set your app up for alpha testing, you can send an invite link to your test accounts. You can find the link in the Google Play console under the APK section, under the “Alpha” tab (and assuming you’ve enabled alpha testing.
+
+
+
+The format of the link is `[https://play.google.com/apps/testing/](https://play.google.com/apps/testing/ “Owner” for now just so we don’t run into permissions issues. You’ll probably want to investigate further to fine a more limited role that only allows receipt verification, but for now, I don’t want any unnecessary road blocks for getting this to work. We’re probably going to run into “permission denied” errors at first anyways, so the fewer reasons for this, the better.
+
+ 8. It will auto-generate an account ID for you.
+
+ 9. Finally, for the “Key type”, select “JSON”. Then click the “Create” button.
+
+This should prompt the download of a JSON file that will have contents similar to the following:
+
+
+ {
+ "type": "service_account",
+ "project_id": "iapdemo-152500",
+ "private_key_id": "1b1d39f2bc083026b164b10a444ff7d839826b8a",
+ "private_key": "-----BEGIN PRIVATE KEY----- ... some private key string -----END PRIVATE KEY-----n",
+ "client_email": "[[email protected]](/cdn-cgi/l/email-protection)",
+ "client_id": "117601572633333082772",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://accounts.google.com/o/oauth2/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/iapdemo%40iapdemo-152500.iam.gserviceaccount.com"
+ }
+
+This is where we get the information we’re looking for. The “client_email” is what we’ll use for your `googleClientId`, and the “private_key” is what we’ll use for the `googlePrivateKey`.
+
+__ | Use the “client_email” value as our client ID, not the “client_id” value as you might be tempted to do.
+---|---
+
+We’ll set these in our constants:
+
+
+ public static final String GOOGLE_DEVELOPER_API_CLIENT_ID="[[email protected]](/cdn-cgi/l/email-protection)";
+ public static final String GOOGLE_DEVELOPER_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----n";
+
+ ...
+
+ validator.setGoogleClientId(GOOGLE_DEVELOPER_API_CLIENT_ID);
+ validator.setGooglePrivateKey(GOOGLE_DEVELOPER_PRIVATE_KEY);
+
+**NOT DONE YET**
+
+Before we can use these credentials to verify receipts for our app, we need to link our app to this new service account from within Google Play.
+
+Steps:
+
+ 1. Open the [Google Play Developer Console](https://play.google.com/apps/publish/), then click on “Settings” > “API Access”.
+
+ 2. You should see your app listed on this page. Click the “Link” button next to your app.
+
+
+
+ 3. This should reveal some more options on the page. You should see a “Service Accounts” section with a list of all of the service accounts that you have created. Find the one we just created, and click the “Grant Access” button in its row.
+
+
+
+ 4. This will open a dialog titled “Add New User”. Leave everything default, except change the “Role” to “Administrator”. This provides “ALL” permissions to this account, which probably isn’t a good idea for production. Later on, after everything is working, you can circle back and try to refine permissions. For the purpose of this tutorial, I just want to pull out all of the potential road blocks.
+
+
+
+ 5. Press the “Add User” button.
+
+At this point, the service account **should** be active so we can try to validate receipts.
+
+#### Testing Receipt Validation
+
+The `ReceiptsFacadeREST` class includes a flag to enable/disable play store validation. By default it is disabled. Let’s enable it:
+
+
+ public static final boolean DISABLE_PLAY_STORE_VALIDATION=true;
+
+Change this to `false`.
+
+Then build and run the server app. The `validateSubscriptionsCron()` method is set to run once per minute, so we just need to wait for the timer to come up and it should try to validate all of the play store receipts.
+
+__ | I’m assuming you’ve already added a receipt in the previous test that we did. If necessary, you should purchase the subscription again in your app.
+---|---
+
+After a minute or so, you should see “———– VALIDATING RECEIPTS ———” written in the Glassfish log, and it will validate your receipts. If it works, your receipt’s expiry date will get populated in the database, and you can press “Synchronize Receipts” in your app to see this reflected. If it fails, there will like be a big ugly stack trace and exception readout with some clues about what went wrong.
+
+Realistically, your first attempt will fail for some reason. Use the error codes and stack traces to help lead you to the problem. And feel free to post questions here.
+
+## iTunes Connect Setup
+
+The process for setting up and testing your app on iOS is much simpler than on Android (IMHO). It took me a couple hours to get the iTunes version working, vs a couple days on the Google Play side of things. One notable difference that makes things simpler is that you don’t need to actually upload your app to the store to test in-app purchase. You can just use your debug build on your device. It is also **much** easier to roll a bunch of test accounts than on Google Play. You don’t need to set up an alpha program, you just create a few “test accounts” (and this is easy to do) in your iTunes connect account, and then make sure to use one of these accounts when making a purchase. You can easily switch accounts on your device from the “Settings” app, where you can just log out of the iTunes store – which will cause you to be prompted in your app the next time you make a purchase.
+
+### Setting up In-App Products
+
+The process to add products in iTunes connect is outlined [in this apple developer document](https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html#//apple_ref/doc/uid/TP40013727-CH3-SW1). We’ll add our two SKUs:
+
+ 1. **iapdemo.noads.month.auto** – The 1 month subscription.
+
+ 2. **iapdemo.noads.3month.auto** – The 3 month subscription.
+
+Just make sure you add them as auto-renewable subscriptions, and that you specify the appropriate renewal periods. Use the SKU as the product ID. Both of these products will be added to the same subscription group. Call the group whatever you like.
+
+### Creating Test Accounts
+
+In order to test purchases, you need to create some test accounts. See [this apple document](https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SettingUpUserAccounts.html#//apple_ref/doc/uid/TP40011225-CH25-SW10) for details on how to create these test accounts. Don’t worry, the process is much simpler than for Android. It should take you under 5 minutes.
+
+Once you have the test accounts created, you should be set to test the app.
+
+ 1. Make sure your server is running.
+
+ 2. Log out from the app store. The process is described [here](https://support.apple.com/en-ca/HT203983).
+
+ 3. Open your app.
+
+ 4. Try to purchase a 1-month subscription
+
+If all went well, you should see the receipt listed in the RECEIPTS table of your database. But the expiry date will be null. We need to set up receipt verification in order for this to work.
+
+### Setting up Receipt Verification
+
+In order for receipt verification to work we simply need to generate a shared secret in iTunes connect. The process is described [here](https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html).
+
+Once you have a shared secret, update the ReceiptsFacadeREST class with the value:
+
+
+ public static final String APPLE_SECRET = "your-shared-secret-here";
+
+And enable iTunes store validation:
+
+
+ public static final boolean DISABLE_ITUNES_STORE_VALIDATION=true;
+
+Change this to `false`.
+
+If you rebuild and run the server project, and wait for the `validateSubscriptionsCron()` method to run, it should validate the receipt. After about a minute (or less), you’ll see the text “———– VALIDATING RECEIPTS ———” written to the Glassfish log file, followed by some output from connecting to the iTunes validation service. If all went well, you should see your receipt expiration date updated in the database. If not, you’ll likely see some exception stack traces in the Glassfish log.
+
+__ | Sandbox receipts in the iTunes store are set to run on an accelerated schedule. A 1 month subscription is actually 5 minutes, 3 months is 15 minutes etc… Also sandbox subscriptions don’t seem to persist in perpetuity until the user has cancelled it. I have found that they usually renew only 4 or 5 times before they are allowed to lapse by Apple.
+---|---
+
+## Summary
+
+Setting up in-app purchase is not for the faint of heart. Having to jump through a battery of hoops on Android is poison for the soul. On the other hand, it may all just be worth it. Once you have a working system up and running, it can **mostly** continue to run on its own. Despite the length of this post, I’m really only just scratching the surface on this topic. There are many other aspects that I simply ignored due to time constraints. I encourage you to take the code in this post and try to make it work for yourself. You’re in for some pain, but I guarantee that the reward at the end of it all is worth it.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **bryan** — January 3, 2017 at 9:19 pm ([permalink](https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html#comment-23265))
+
+> Thanks Steve – great tutorial.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautorenewing-subscriptions-in-ios-and-android.html)
+
+
+### **salah Alhaddabi** — August 13, 2017 at 9:46 pm ([permalink](https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html#comment-23501))
+
+> Dear Steve,
+> when I try to run the demo client, I get the following erros: (I have changed the package name to be com.salah.trails.iapdemo.IAPDemo)
+>
+> java.lang.ClassNotFoundException: com.salah.trails.iapdemo.IAPDemo
+> at java.net.URLClassLoader.findClass([URLClassLoader.java]():381)
+> at java.lang.ClassLoader.loadClass([ClassLoader.java]():424)
+> at sun.misc.Launcher$AppClassLoader.loadClass([Launcher.java]():331)
+> at java.lang.ClassLoader.loadClass([ClassLoader.java]():357)
+> at java.lang.ClassLoader.findSystemClass([ClassLoader.java]():1001)
+> at com.codename1.impl.javase.ClassPathLoader.findClass([ClassPathLoader.java]():100)
+> at com.codename1.impl.javase.ClassPathLoader.loadClass([ClassPathLoader.java]():50)
+> at java.lang.Class.forName0(Native Method)
+> at java.lang.Class.forName([Class.java]():264)
+> at com.codename1.impl.javase.Executor$[1.run](:86)
+> at java.awt.event.InvocationEvent.dispatch([InvocationEvent.java]():311)
+> at java.awt.EventQueue.dispatchEventImpl([EventQueue.java]():756)
+> at java.awt.EventQueue.access$500([EventQueue.java]():97)
+> at java.awt.EventQueue$[3.run](:709)
+> at java.awt.EventQueue$[3.run](:703)
+> at java.security.AccessController.doPrivileged(Native Method)
+> at java.security.ProtectionDomain$1.doIntersectionPrivilege([ProtectionDomain.java]():75)
+> at java.awt.EventQueue.dispatchEvent([EventQueue.java]():726)
+> at java.awt.EventDispatchThread.pumpOneEventForFilters([EventDispatchThread.java]():201)
+> at java.awt.EventDispatchThread.pumpEventsForFilter([EventDispatchThread.java]():116)
+> at java.awt.EventDispatchThread.pumpEventsForHierarchy([EventDispatchThread.java]():105)
+> at java.awt.EventDispatchThread.pumpEvents([EventDispatchThread.java]():101)
+> at java.awt.EventDispatchThread.pumpEvents([EventDispatchThread.java]():93)
+> at [java.awt.EventDispatchThrea…](:82)
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautorenewing-subscriptions-in-ios-and-android.html)
+
+
+### **Shai Almog** — August 14, 2017 at 7:04 am ([permalink](https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html#comment-23511))
+
+> You refactored the package name without updating it in the [codenameone_settings.proper…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautorenewing-subscriptions-in-ios-and-android.html)
+
+
+### **Brenden** — June 11, 2019 at 9:15 am ([permalink](https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html#comment-24009))
+
+> Dear Steve
+>
+> Above it says that there is no need to upload your app to the apple store to test the app , is this still valid now in 2019 ? or has this changed ?
+>
+> Regards Brenden
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautorenewing-subscriptions-in-ios-and-android.html)
+
+
+### **Shai Almog** — June 12, 2019 at 4:11 am ([permalink](https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html#comment-24099))
+
+> Hi,
+> it should work fine locally but you need to still configure everything in itunes connect otherwise it won’t work.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautorenewing-subscriptions-in-ios-and-android.html)
+
+
+### **Rochana Sawatzky** — November 27, 2019 at 1:36 am ([permalink](https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html#comment-24269))
+
+> Hey Steve – no matter what I seem to do, when trying to ant-install deps I get the error:
+>
+> Cannot run program “mvn” (in directory …”): CreateProcess error=2, The system cannot find the file specified
+>
+> I have installed maven, and added it to my path, as well as adding maven_home, m2_home, java_home environment variables. I can run mvn -version fine, so I’m at a loss for why I’m getting the error. Any ideas?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautorenewing-subscriptions-in-ios-and-android.html)
+
+
+### **Shai Almog** — November 27, 2019 at 2:59 am ([permalink](https://www.codenameone.com/blog/autorenewing-subscriptions-in-ios-and-android.html#comment-24267))
+
+> If you added it to the path in Windows GUI it might not impact the currently open shell. If you’re using windows shell this might conflict with spaces you have in the path so make sure you use quotes e.g. `set PATH=”PATH TO MAVEN”;%PATH%`
+>
+> If this doesn’t help try to provide more details about your environment so we can help.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautorenewing-subscriptions-in-ios-and-android.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/autosizing-add-all-ios-redirects.md b/docs/website/content/blog/autosizing-add-all-ios-redirects.md
new file mode 100644
index 0000000000..47757d71e0
--- /dev/null
+++ b/docs/website/content/blog/autosizing-add-all-ios-redirects.md
@@ -0,0 +1,329 @@
+---
+title: Autosizing, Add All & iOS Redirects
+slug: autosizing-add-all-ios-redirects
+url: /blog/autosizing-add-all-ios-redirects/
+original_url: https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html
+aliases:
+- /blog/autosizing-add-all-ios-redirects.html
+date: '2017-02-07'
+author: Shai Almog
+---
+
+
+
+One of the common requests we received over the years is a way to let text “fit” into the allocated space so the font will match almost exactly the width available. In some designs this is very important but it’s also very tricky. Measuring the width of a String is a surprisingly expensive operation on some OS’s. Unfortunately, there is no other way other than trial & error to find the “best size”.
+
+Still despite the fact that something is “slow” we might still want to use it for some cases, this isn’t something you should use in a renderer, infinite scroll etc. and we recommend minimizing the usage of this feature as much as possible.
+
+This feature is only applicable to `Label` and its subclasses (e.g. `Button`), with components such as `TextArea` (e.g. `SpanButton`) the choice between shrinking and line break would require some complex logic.
+
+To activate this feature just use `setAutoSizeMode(true)` e.g.:
+
+
+ Form hi = new Form("AutoSize", BoxLayout.y());
+
+ Label a = new Label("Short Text");
+ a.setAutoSizeMode(true);
+ Label b = new Label("Much Longer Text than the previous line...");
+ b.setAutoSizeMode(true);
+ Label c = new Label("MUCH MUCH MUCH Much Longer Text than the previous line by a pretty big margin...");
+ c.setAutoSizeMode(true);
+
+ Label a1 = new Button("Short Text");
+ a1.setAutoSizeMode(true);
+ Label b1 = new Button("Much Longer Text than the previous line...");
+ b1.setAutoSizeMode(true);
+ Label c1 = new Button("MUCH MUCH MUCH Much Longer Text than the previous line by a pretty big margin...");
+ c1.setAutoSizeMode(true);
+ hi.addAll(a, b, c, a1, b1, c1);
+
+ hi.show();
+
+
+
+Figure 1. Automatically sizes the fonts of the buttons/labels based on text and available space
+
+### Add All
+
+You will notice in the code above we added a new method: `addAll`.
+
+`addAll` is a shortcut that allows the code above to be written as:
+
+
+ hi.addAll(a, b, c, a1, b1, c1);
+
+Instead of the more verbose syntax:
+
+
+ hi.add(a).
+ add(b).
+ add(c).
+ add(a1).
+ add(b1).
+ add(c1);
+
+It’s not a huge difference but at least when building demos/test cases it’s nice.
+
+### Redirects on iOS
+
+One of the big decisions we made in Codename One was to not copy `java.io` wholesale. This has been a double edged sword…
+
+It has made us far more portable and also provided reliability that no other competing service can match in terms of networking. However, the differences within the network stack between OS’s are second only to the GUI differences. One such painful difference is the fact that iOS requires HTTPS now.
+
+Another such painful difference is redirect behavior. Codename One handles redirect by returning the 30x HTTP response and redirecting seamlessly. However, you can override that behavior and grab the 30x redirect. This also means the behavior of redirect (which is one of those gray areas in HTTP implementations) is consistent.
+
+But this isn’t the case on iOS where it handles redirect internally and we are faced with this after the fact.
+
+In the past we evaluated this and determined that this wouldn’t be an easy fix, I’m not sure if this is something we missed or something that changed in recent iOS versions but it looks like the fix isn’t as hard as we feared as we got this [pull request](https://github.com/codenameone/CodenameOne/pull/2030) & merged it.
+
+We might still revert this fix if we run into too many problems so with this Friday update check out your networking code and make sure everything is in order, if not we might need to provide a build hint to toggle this.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **João Bastos** — February 13, 2017 at 12:32 pm ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23210))
+
+> Is the addAll shortcut already available? Cant see it in netbeans…
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — February 14, 2017 at 8:05 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23119))
+
+> Shai Almog says:
+>
+> It should be. Use the Update Client Libs button in Codename One Settings.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **João Bastos** — February 14, 2017 at 5:23 pm ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24230))
+
+> João Bastos says:
+>
+> Solved! Thanks Shai!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 20, 2018 at 8:06 pm ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24001))
+
+> Denis says:
+>
+> Hi Shai,
+>
+> setAutoSizeMode(true) doesn’t work for my app, more over it makes text to disappear
+> please take at screenshots, this is before AutoSize set to true, text is very tiny in tablets
+> [https://uploads.disquscdn.c…]()
+> and this is after AutoSize set to true for first (top) label
+> [https://uploads.disquscdn.c…]()
+>
+> what can a reason for that ?
+>
+> Thanks,
+> Denis
+>
+> p.s. CodenameOne version 5.0
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 21, 2018 at 10:38 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24033))
+
+> Denis says:
+>
+> update, text appears on real devices, but it’s very very tiny
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 22, 2018 at 6:09 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23934))
+
+> Shai Almog says:
+>
+> It looks like you used something such as absolute center or flow layout. That won’t work. These layout managers give components their preferred size which means the resizing text will shrink and won’t grow. You need to use a layout that gives out the full width.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 23, 2018 at 9:02 pm ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24070))
+
+> Denis says:
+>
+> yes, you are right, in some parts of UI I used flow layout. Is there any other way to make text bigger on tablets ? because it’s really very very tiny on 10 inch tablets, is it possible to set font size for “Label” UUID (to apply it to all labels at once) depending on device screen size ?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 24, 2018 at 4:33 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24082))
+
+> Shai Almog says:
+>
+> You can do that in the theme. See the section in the developer guide about theme layering. You can add a theme on top of the current theme.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 24, 2018 at 6:54 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23785))
+
+> Denis says:
+>
+> I see, that’s better than maintain different APKs for different devices (phones, tablets), but still needs some logic to figure out on which device app is currently running and there should it load secondary theme or not, is there some handy way ?
+> or I just go with one of these
+> Display.getInstance().getDeviceDensity()
+> Display.getInstance().getDisplayWidth()
+> Display.getInstance().getDisplayHeight()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 25, 2018 at 8:23 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24019))
+
+> Shai Almog says:
+>
+> There’s isTablet() both in Display & the CN class.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 25, 2018 at 12:05 pm ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24025))
+
+> Denis says:
+>
+> cool, thanks ! just tried that, interesting, but font changes doesn’t apply, background color does, i.e. I have correct layered theme and code setup, I see different background for tablets, but font size doesn’t change, I am trying t set to “True Type: native:MainRegular” and “True Type Size: Large”, but nothing happens on tablets, I tried both “[Default Style]” and “Label”, “Button” individually, can you please advise ?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 26, 2018 at 9:01 pm ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24052))
+
+> Denis says:
+>
+> setting “True Type Size” to millimeters value on component level helped, [Default Style] still doesn’t work even with millimeters value, I set font site for Buttons and Labels, but for example Dialog title is still very tiny, looks like I should set values for all components individually
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 27, 2018 at 8:48 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24084))
+
+> Denis says:
+>
+> also Dialogs for some reason looks differently on mobile and tables, with the same theme (and no layered themes)
+> mobile
+> [https://uploads.disquscdn.c…]()
+> tablet [https://uploads.disquscdn.c…]()
+>
+> all in simulator haven’t test on real devices yet
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 27, 2018 at 10:43 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23964))
+
+> Denis says:
+>
+> on real tablet device dialog title appears similar to mobile, but with less spacing from top and bottom
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 28, 2018 at 5:31 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24048))
+
+> Shai Almog says:
+>
+> Which tablet skin? It’s possible the skin is out of date and needs a new theme
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — September 28, 2018 at 7:17 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23866))
+
+> Denis says:
+>
+> there are different skins for the same device name, for example for iPad Pro ([ipad-pro.skin]() and [iPadPro.skin]()), iPhoneX (this one is strangest [iPhoneX.skin]() and /[iPhoneX.skin]()) and Galaxy S7 ([GalaxyS7.skin]() and [SamsungGalaxyS7.skin]()), which is a bit confusing, but it would be easier if skins sorted by device name
+>
+> the skin in above mentioned issue is [IPadPro.skin](), but I have compared to [Nexus5.skin](), different platform, I didn’t though about that, [MicrosoftSurfacePro4.skin]() also have different view of Dialogs, but again it’s another platform, so may there is no issue at all, I have’t real Apple device to compare
+>
+> p.s.
+> I also get these errors when I changing a skin, simulator crashes and I have to start it again, but it works after that
+> java.lang.UnsatisfiedLinkError: Native Library C:UsersDenisAppDataLocalTempsqlite-3.7.151-amd64-sqlitejdbc.dll already loaded in another classloader
+> java.lang.UnsatisfiedLinkError: org.sqlite.NativeDB._open(Ljava/lang/String;I)V
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 29, 2018 at 4:27 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24069))
+
+> Shai Almog says:
+>
+> Did you refresh theme? It’s a bit hard to guess with that amount of information.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 29, 2018 at 4:31 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24074))
+
+> Shai Almog says:
+>
+> The iPad skin will look like iOS where the dialogs have a different default design inherited from te native theme.
+>
+> The simulator crash is due to sqlite, we tried multiple ways to workaround it but it seems that the sqlite JDBC support is averse to class loaders. If you use sqlite switching skins will crash and you’ll have to re-run the app. There’s this issue which we tried and failed to fix multiple times [https://github.com/codename…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 30, 2018 at 9:27 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24044))
+
+> Shai Almog says:
+>
+> Default will only work for things that aren’t explicitly defined. Since the title is explicitly defined in the native theme you need to override that.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — September 30, 2018 at 9:28 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-21580))
+
+> Shai Almog says:
+>
+> That mostly relates to the density of the device
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — October 1, 2018 at 9:07 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-24087))
+
+> Denis says:
+>
+> that makes sense, and I thought the same, the only problem is that we can’t see what defined in native theme ))) I have just 6 items in theme settings, Default style, Button, Container, Label, Multibutton and Toolbar (only Padding/Margin) and as I understand because they are explicitly defined in main theme I have to define them also in layered theme, [Default Style] in layered theme will not override their parameters from main theme, is that correct ?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Shai Almog** — October 2, 2018 at 4:49 am ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23822))
+
+> Shai Almog says:
+>
+> Technically you can open the native theme file from our git repo, but that’s probably not a good idea since that might change. Yes you need to explicitly define things you want to change. E.g. the title in iOS is center aligned and in Android it’s left aligned. We usually don’t override alignment to keep that default behavior.
+> The nice thing is that most of these things can be tested live with the simulator and switching is relatively quick (with the exception of the SQLite problem).
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+
+### **Denis** — October 2, 2018 at 12:21 pm ([permalink](https://www.codenameone.com/blog/autosizing-add-all-ios-redirects.html#comment-23893))
+
+> Denis says:
+>
+> Thank you Shai, I do exactly the same, use emulator to adjust components, it’s very useful, thanks !
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fautosizing-add-all-ios-redirects.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/avoiding-lists.md b/docs/website/content/blog/avoiding-lists.md
new file mode 100644
index 0000000000..3fe8cf8849
--- /dev/null
+++ b/docs/website/content/blog/avoiding-lists.md
@@ -0,0 +1,389 @@
+---
+title: Avoiding Lists
+slug: avoiding-lists
+url: /blog/avoiding-lists/
+original_url: https://www.codenameone.com/blog/avoiding-lists.html
+aliases:
+- /blog/avoiding-lists.html
+date: '2016-08-21'
+author: Shai Almog
+---
+
+
+
+When picking up a new UI API people often start with a list of items. Lists are often used for navigation, logic and data so it’s a natural place to start. Codename One’s List class is a bad place to start though… It’s complex and encumbered and has far better alternatives.
+
+### How did we Get Here?
+
+When we initially created the `List` API we were heavily inspired by Swing’s architecture. The ability to create an infinitely sized list without a performance penalty was attractive and seemed like a good direction for our original 2mb RAM target devices (back in 2006-7). We knew the renderer/model approach was hard for developers to perceive but we also assumed a lot of Swing developers would find it instantly familiar.
+
+We made attempts to improve `List` in the years since e.g.: `MultiList`, `GenericListCellRenderer`, `ContainerList` etc.
+
+These helped but the core problems of `List` remain.
+
+### What Changed?
+
+Modern interfaces are far more dynamic, we have features such as swipable containers, drag and drop to rearrange etc. The renderer approach complicates trivial tasks e.g.:
+
+ * Variable sized entries – this is impossible in a standard `List` or `MultiList`. We designed `ContainerList` to solve this but it’s both ridiculously inefficient and buggy
+
+ * More than one clickable item per row – it’s common to have more than one item within a row in the `List` that can handle an event. E.g. a delete button. This is a difficult (albeit possible) task for `List` items.
+
+ * Performance – `List` can perform well but writing performant `List` code is a challenge. Anything under 5000 entries would perform better with alternative solutions. If you need more than 5000 rows, reconsider…
+Scrolling beyond 1000 rows on a mobile device is challenging.
+
+ * Customizability – You can customize the look of the `List` component but there are nuances and some limits.
+
+ * Model – MVC is a good idea but it’s hard. Features like dynamic image download in lists challenge even experienced Codename One developers.
+
+### What Should we use Instead?
+
+This varies based on your needs but the general answer is a scrollable `BoxLayout.Y_AXIS` container.
+
+#### The Simple Use Case
+
+E.g. if I write a simple `List` such as this:
+
+
+ com.codename1.ui.List lst = new com.codename1.ui.List("A", "B", "C");
+ lst.addActionListener(e -> Log.p("You picked: " + lst.getSelectedItem()));
+
+I can convert it to this:
+
+
+ String[] abc = new String[] {"A", "B", "C"};
+ Container list = new Container(BoxLayout.y());
+ list.setScrollableY(true);
+ for(String s : abc) {
+ Button b = new Button(s);
+ list.add(b);
+ b.addActionListener(e -> Log.p("You picked: " + b.getText()));
+ }
+
+Admittedly there is more code in the second version, but it’s far more powerful and as your UI design grows the code will shrink by comparison!
+
+E.g. if you don’t want the default look of the list or want thumbnail image, or want a single entry to behave differently the latter option is far simpler.
+
+#### Lead Component
+
+When you click an entry within the list you can click anywhere and it will work. If you compose an entry in the list from more than one piece those pieces act as one.
+
+E.g. we have this code in the developer guide section covering `List` renderers:
+
+
+ class ContactsRenderer extends Container implements ListCellRenderer {
+ private Label name = new Label("");
+ private Label email = new Label("");
+ private Label pic = new Label("");
+ private Label focus = new Label("");
+
+ public ContactsRenderer() {
+ setLayout(new BorderLayout());
+ addComponent(BorderLayout.WEST, pic);
+ Container cnt = new Container(new BoxLayout(BoxLayout.Y_AXIS));
+ name.getAllStyles().setBgTransparency(0);
+ name.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
+ email.getAllStyles().setBgTransparency(0);
+ cnt.addComponent(name);
+ cnt.addComponent(email);
+ addComponent(BorderLayout.CENTER, cnt);
+
+ focus.getStyle().setBgTransparency(100);
+ }
+
+ public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {
+ Contact person = (Contact) value;
+ name.setText(person.getName());
+ email.setText(person.getEmail());
+ pic.setIcon(person.getPic());
+ return this;
+ }
+
+ public Component getListFocusComponent(List list) {
+ return focus;
+ }
+ }
+
+We can create a similar container using this approach:
+
+
+ Container list = new Container(BoxLayout.y());
+ list.setScrollableY(true);
+ for(Contact c : contacts) {
+ list.add(createContactContainer(c));
+ }
+
+ private Container createContactContainer(Contact person) {
+ Label name = new Label("");
+ Label email = new Label("");
+ Label pic = new Label("");
+ Container cnt = new Container(new BoxLayout(BoxLayout.Y_AXIS));
+ name.getAllStyles().setBgTransparency(0);
+ name.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
+ email.getAllStyles().setBgTransparency(0);
+ cnt.add(name);
+ cnt.add(email);
+ name.setText(person.getName());
+ email.setText(person.getEmail());
+ pic.setIcon(person.getPic());
+ return BorderLayout.center(cnt).
+ add(BorderLayout.EAST, pic);
+ }
+
+The problem with this approach becomes obvious when we try to add an event listener….
+
+We can make `name` into a `Button` but then what happens when a user clicks `email`?
+
+We can make all the entries into buttons but that isn’t practical. That’s what [lead component](https://www.codenameone.com/manual/components.html#lead-component-sidebar) is for, we can make one component into a button and it “takes the lead”. If we make name into a button and set it as the lead of the `Container` it will handle all the events and state changes for the entire row!
+
+__ | For more information on lead components check out [the sidebar](https://www.codenameone.com/manual/components.html#lead-component-sidebar) in the developer guide.
+---|---
+
+We can change the code above like this and support lead components:
+
+
+ private Container createContactContainer(Contact person) {
+ Button name = new Button("", "Label");
+ name.addActionListener(e -> Log.p("You clicked: " + person));
+ // ...
+ Container b = BorderLayout.center(cnt).
+ add(BorderLayout.EAST, pic);
+ b.setLeadComponent(name);
+ return b;
+ }
+
+__ | What do you do if you want to exclude an item from the lead component hierarchy (e.g. a delete button)?
+Check out [this blog post](/blog/unleading-mutating-accordion.html).
+---|---
+
+#### Infinite Scroll
+
+One of our earliest demos showed off a million entries running on a 3mb Nokia mobile phone. While that is an impressive feat it isn’t useful.
+
+Most real world UI’s use pagination to fetch more data when they reach the bottom of the scroll. This is predictable and easy to integrate both in the client and server code.
+
+Two classes simplify the process of infinite scrolling list: `InfiniteContainer` and `InfiniteScrollAdapter`.
+
+`InfiniteContainer` is an easy to use drop-in replacement to `Container`. `InfiniteScrollAdapter` is more versatile, you can apply it to any `Container` including the content pane. We have samples for both [InfiniteContainer](https://www.codenameone.com/javadoc/com/codename1/ui/InfiniteContainer.html) and [InfiniteScrollAdapter](https://www.codenameone.com/javadoc/com/codename1/components/InfiniteScrollAdapter.html) in the JavaDocs.
+
+### Don’t Use Lists
+
+In closing I’d like to re-iterate our recommendation: “Don’t use lists”. We didn’t deprecate those API’s because developers rely heavily on them & this might induce “panic”.
+There’s no valid reason to use a `List` as opposed to a `Container`. `List` is harder to use, slower & not as flexible.
+
+We can’t cover every conceivable use case in this post so if you have a `List` or code you can’t imagine any other way, post it in the comments below.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **bryan** — August 22, 2016 at 9:14 pm ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23004))
+
+> bryan says:
+>
+> Agree with all this. I originally used Lists with custom cell renderers, and with the deprecation of the old GUI builder, I took the opportunity to refactor my code and change all Lists to Containers. Initially my thoughts were “it can’t work as well”, but in fact there appears to be zero performance penalty, and as Shai says, you can create a much better UI experience. Don’t use Lists !
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Sadart** — August 23, 2016 at 4:31 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-22749))
+
+> Sadart says:
+>
+> True. Lists are horrible to deal with. I am still trying to recall when I used them. Stayed away from them years ago because stacking up containers made sense to me.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Jérémy MARQUER** — August 23, 2016 at 7:35 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-22712))
+
+> Jérémy MARQUER says:
+>
+> Totally agree and happy to read this post !! I initially work with complex List but I have refactored it recently. For example, I InfiniteProgress doesn’t animate correctly in items of my List -> I have changed it to InfiniteContainer and it works better !!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Shai Almog** — August 24, 2016 at 3:58 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23011))
+
+> Shai Almog says:
+>
+> A user posted a question about searching within a list using the filter proxy model. That’s a great question that he seems to have deleted…
+>
+> The Toolbar JavaDoc contains two samples of searching within a container: [https://www.codenameone.com…]()
+>
+> Which also shows off animation within the search and quite a few other nice things. Notice that this isn’t demonstrated with an infinite container because searching thru that would require fetching all the data which might not be what you want to do so you will need to adapt the code to work with fetch logic (e.g. special webservice call for search like we do in property cross).
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Carlos** — August 24, 2016 at 8:09 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-22937))
+
+> Carlos says:
+>
+> I did not delete the post, I have no idea what happened with it. Anyway, this is the code I posted before (thank you for the samples):
+>
+> textoFiltro.addDataChangeListener((int type, int index) -> {
+>
+> filtraProxy(listaRecetas, textoFiltro);
+>
+> });
+> ……..
+>
+> private void filtraProxy(final List listaRecetas, TextField textoFiltro) {
+>
+> Form f = Display.getInstance().getCurrent();
+>
+> FilterProxyListModel listaFiltro;
+>
+> if (listaRecetas.getModel() instanceof FilterProxyListModel) {
+>
+> listaFiltro = (FilterProxyListModel) listaRecetas.getModel();
+>
+> } else {
+>
+> if(textoFiltro.getText().length() == 0) {
+>
+> return;
+>
+> }
+>
+> listaFiltro = new FilterProxyListModel(listaRecetas.getModel()) {
+>
+> @Override
+>
+> protected boolean check(Object o, String str) {
+>
+> Hashtable h = (Hashtable) o;
+>
+> Object textoHash = h.get(“Listado”);
+>
+> return super.check(textoHash, str);
+>
+> }
+>
+> };
+>
+> listaRecetas.setModel(listaFiltro);
+>
+> }
+>
+> if (textoFiltro.getText().length() == 0) {
+>
+> listaRecetas.setModel(listaFiltro.getUnderlying());
+>
+> } else {
+>
+> listaFiltro.filter(textoFiltro.getText());
+>
+> }
+>
+> f.revalidate();
+>
+> }
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Shai Almog** — August 25, 2016 at 5:21 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-21457))
+
+> Shai Almog says:
+>
+> Odd. I’ve seen messages disappear before but I always assumed they were deleted by the asker…
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Jeff Crump** — September 1, 2016 at 5:44 pm ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-22795))
+
+> Jeff Crump says:
+>
+> I would prefer to continue to use the existing List class. When I first started to use Codename One I created an extend List class that utilizes a separate listmodel class, a multi-threaded downloader class, and a renderer class (which generates a prototype). I am able to place buttons, text and other components in the renderer class and have it manage states, mutable backgrounds and pass events to handle unique responses. The downloader class initially pulls two pages of images, then as the list scrolls it downloads additional pages, four at a time, then pauses until the next scroll. The list model class only fires an update when the image is still visible. My ListModel class also implements static filters on the data as the model is instantiated. All of my lists reside on tabs and work/scroll very well. The downloader class uses a two tier thread safe CacheMap. It easily handles 5,000 cells as the cell cache scrolls both directions. It is very fast and doesn’t suffer from pauses or jerky responses.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Shai Almog** — September 2, 2016 at 5:16 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-22880))
+
+> Shai Almog says:
+>
+> That might be one of the rare use cases for which list is indeed still superior.
+>
+> I think it’s pretty rare because navigating 5000 entries on mobile devices is probably too much for users and obviously the effort you had to put to get it going was pretty big… That’s the gist of this post. Yes there are edge cases that list can handle well but they are edge cases.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Jeff Crump** — September 2, 2016 at 12:52 pm ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23039))
+
+> Jeff Crump says:
+>
+> I know, I just didn’t want to lose the existing List component. I wrote most of my code during the first few months after I started using Codename One. It was still very new and I had decided to go with it as is. So I added in what I wanted and worked around the rest. There have been many new components and upgrades since then and it has become a very capable platform. While we have tested very large lists, our target is actually about 500, and with the internal filters the length is between 65 and 85.
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **khmaies hassen** — April 20, 2017 at 10:32 pm ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23227))
+
+> khmaies hassen says:
+>
+> when i reach the end of the list where there are no new pages to show and then i go up and pull the list to refresh, it gives me an empty page. how to reset “pageNumber” to 1 when i use pull to refresh?
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Shai Almog** — April 21, 2017 at 4:44 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23455))
+
+> Shai Almog says:
+>
+> In infinite container?
+>
+> Place a breakpoint in your callback code and make sure you return the right value on every call
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **khmaies hassen** — April 21, 2017 at 8:58 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-21466))
+
+> khmaies hassen says:
+>
+> Even in your example the same thing happenes when you reach the end
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Shai Almog** — April 22, 2017 at 8:24 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23422))
+
+> Shai Almog says:
+>
+> Which example
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **khmaies hassen** — April 22, 2017 at 12:22 pm ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23428))
+
+> khmaies hassen says:
+>
+> [https://www.codenameone.com…]()
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+
+### **Shai Almog** — April 23, 2017 at 5:33 am ([permalink](https://www.codenameone.com/blog/avoiding-lists.html#comment-23521))
+
+> Shai Almog says:
+>
+> I think that’s code that originally relied on InfiniteScrollAdapter which has no pull to refresh or index… You need to use the index value to determine the page you are on with InfiniteContainer
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Favoiding-lists.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/back-vacation.md b/docs/website/content/blog/back-vacation.md
new file mode 100644
index 0000000000..f65706e36c
--- /dev/null
+++ b/docs/website/content/blog/back-vacation.md
@@ -0,0 +1,95 @@
+---
+title: We're Back from Vacation
+slug: back-vacation
+url: /blog/back-vacation/
+original_url: https://www.codenameone.com/blog/back-vacation.html
+aliases:
+- /blog/back-vacation.html
+date: '2019-09-02'
+author: Shai Almog
+---
+
+
+
+Summer is finally over and the kids are going back to school/kindergarten so it’s time to go back to our regularly scheduled posts. I won’t be posting as often as before as I’ll dedicate more time for support/development activities but still there’s a lot to write about…
+
+During our time off we had a lot of changes, I’ll repeat a few big ones which you might have run into already and cover a few that might have gone under the radar.
+
+### GCM Removal and FCM as Default
+
+During the month of August Google finally removed their old GCM servers. We’ve prepared for this ages ago but this still took us a bit off guard. We were ready for the switch itself but there were still a couple of things we weren’t prepared for.
+
+Users who still used the old style of push notifications (prior to the `google-services.json` file approach) had push messages blocked. That was expected.
+
+__ | You can read about the modern approach to push [here](https://www.codenameone.com/manual/push.html)
+---|---
+
+Because that no longer works anyway we switched the default build mode to FCM. This solves an issue for developers who neglected to define the `android.messagingService=fcm` build hint (which you no longer need). However, this causes a build error if you don’t have that JSON file in place. You can get this to compile for now by explicitly stating the build hint `android.messagingService=gcm`. However, push won’t work if you do that since the Google run GCM push servers are no longer there. But it will compile which is a start.
+
+To migrate to the new FCM approach check out the [developer guide section on push](https://www.codenameone.com/manual/push.html).
+
+### API Level 28 and HTTPS Requirement
+
+We migrated the build servers to Android API level 28 as required by Google. This migration was a bit painful because Google changed the way clipping works under Android so we had to make some extensive changes to our rendering pipeline.
+
+However, one thing we can’t mitigate is that Google now blocks HTTP connections (not HTTPS).This is generally a good practice and a requirement on iOS as well. However, if you have an HTTP URL you need to use you can do so with the build hint:
+
+
+ android.xapplication_attr=android:usesCleartextTraffic="true"
+
+### Component Inspector Enhancements
+
+We implemented a couple of RFEs in the venerable component inspector, specifically [2695](https://github.com/codenameone/CodenameOne/issues/2695) and [1476](https://github.com/codenameone/CodenameOne/issues/1476).
+
+You can now refresh the component tree and the selection would remain in place but more importantly you can select a component and it will be highlighted in the UI. This is very helpful as a debugging tool.
+
+
+
+Figure 1. Component inspector highlights current selection
+
+### AutoCompleteTextComponent and TextComponentPassword
+
+[Francesco Galgani](https://github.com/jsfan3) contributed an implementation of
+[AutoCompleteTextComponent](https://github.com/codenameone/CodenameOne/issues/2705) which allows you to use an auto-complete with the text component framework for a more fluid input UI.
+
+He also contributed [TextComponentPassword](https://github.com/codenameone/CodenameOne/issues/2654) which is a password field with the same convention. It carries the more modern “show password” icon convention which is far more convenient than the old “double type” approach.
+
+### Error Callbacks for URLImage
+
+It’s hard to handle errors in `URLImage` objects. Because they are so “seamless” the point for exception handling is deep withing the class. To solve issue [2703](https://github.com/codenameone/CodenameOne/issues/2703) we had to do something different.
+
+You can now use the static method `setExceptionHandler` on `URLImage`. It accepts the inner interface `ErrorCallback` which has a single method:
+
+
+ public static interface ErrorCallback {
+ public void onError(URLImage source, Exception err);
+ }
+
+So effectively you can do something like this:
+
+
+ URLImage.setExceptionHandler((img, err) -> handleError());
+
+### So Much More
+
+I’ll write about other things in the coming weeks as this post is getting a bit long. There’s a lot to cover.
+---
+
+## Archived Comments
+
+_This post was automatically migrated from the legacy Codename One blog. The original comments are preserved below for historical context. New discussion happens in the Discussion section._
+
+
+### **Francesco Galgani** — September 7, 2019 at 7:57 pm ([permalink](https://www.codenameone.com/blog/back-vacation.html#comment-24112))
+
+> The Component Inspector enhancements are very helpful! Thank you very much!
+>
+> [Log in to Reply](https://www.codenameone.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fback-vacation.html)
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/background-fetch.md b/docs/website/content/blog/background-fetch.md
new file mode 100644
index 0000000000..e15230c4dc
--- /dev/null
+++ b/docs/website/content/blog/background-fetch.md
@@ -0,0 +1,368 @@
+---
+title: Background Fetch
+slug: background-fetch
+url: /blog/background-fetch/
+original_url: https://www.codenameone.com/blog/background-fetch.html
+aliases:
+- /blog/background-fetch.html
+date: '2016-06-20'
+author: Steve Hannah
+---
+
+
+
+Background fetch allows an app to periodically “fetch” information from the network while the app is in
+the background. This is scheduled by the native platform, where apps that support background
+fetch will be started up (in the background), and their `performBackgroundFetch` method will be invoked.
+
+__ | Since the app will be launched directly to the background, you cannot assume that the
+`start()` method was invoked prior to the `performBackgroundFetch` call
+---|---
+
+### Implementing Background Fetch
+
+Apps that wish to implement background fetch must implement the
+[BackgroundFetch](https://www.codenameone.com/javadoc/com/codename1/background/BackgroundFetch.html)
+interface in their main class.
+
+__ | The main class is the one mentioned in the preferences not the state machine or some other class!
+---|---
+
+On iOS, you also need to include `fetch` in the list of background modes specifically include `fetch` in the
+`ios.background_modes` build hint e.g.:
+
+
+ ios.background_modes=fetch
+
+Or for more than one mode:
+
+
+ ios.background_modes=fetch,music
+
+In addition to implementing the `BackgroundFetch` interface, apps must explicitly set the background fetch
+interval by invoking `Display.getInstance().setPreferredBackgroundFetchInterval(interval)` at some point, usually
+in the `start()` or `init()` method.
+
+### Platform Support
+
+Currently background fetch is supported on iOS, Android, and in the Simulator (simulated using timers when the
+app is paused). You should use the `Display.getInstance().isBackgroundFetchSupported()` call to check if the
+current platform supports it.
+
+### Sample
+
+The following code demonstrates simple usage of the API:
+
+
+ /**
+ * A simple demo showing the use of the Background Fetch API. This demo will load
+ * data from the Slashdot RSS feed while it is in the background.
+ *
+ * To test it out, put the app into the background (or select Pause App in the simulator)
+ * and wait 10 seconds. Then open the app again. You should see that the data is loaded.
+ */
+ public class BackgroundFetchTest implements BackgroundFetch {
+
+ private Form current;
+ private Resources theme;
+ List