Profile
Back to NewsBack
GitHub Trending 27 min
Reader Mode
openyida/openyida: Your own personal YiDA AI assistant!

openyida/openyida: Your own personal YiDA AI assistant!

15 hours ago

!OpenYida

OpenYida

AI-native CLI for building DingTalk Yida low-code applications.

OpenYida connects AI coding agents with Yida's low-code platform, so developers can create apps, forms, workflows, custom pages, reports, integrations, and deployment configuration from a normal chat-driven development workflow.

Quick Start · 帮助网站&文档 · Capabilities · Full Capability List · CLI Reference · Examples · Contributing · Changelog · Application Previews

npm version</a> npm downloads</a> CI</a> License: MIT</a> Node.js >=18</a>

English README · 简体中文 README


What OpenYida Provides

OpenYida is a bridge between AI coding tools and Yida. It gives agents a stable openyida CLI to create resources, inspect schemas, generate source code, compile and publish pages, and diagnose issues.

The result remains a native Yida application: teams can continue editing it in Yida, use existing enterprise security controls, and deploy through the Yida platform.

Application Previews

Example applications built with the themes below. Click an image to view it at full size. These are visual references, not ready-made business applications: content, data, brand colors, and layouts are adapted to your requirements.

Dark Rail · Fine Lines
Dark Rail · Fine Lines example application
Soft Inset Surfaces
Soft Inset Surfaces example application
Sage
Sage example application
Teal
Teal example application
Terminal
Terminal example application
Platinum
Platinum example application

Use an application theme or an independent creative direction. Describe a style you like, or provide a reference website or design image to customize your app’s appearance. Browse more application examples.

Core Capabilities

| Area | What you can do | |------|-----------------| | Application delivery | Create, update, export, and import Yida applications | | Form modeling | Create forms, update fields, inspect schemas, and manage permissions | | Custom pages | Generate React-based pages, lint Yida runtime rules, compile, and publish | | Workflow automation | Create process forms, configure approval flows, preview process instances | | Data operations | Query form/process/task/subform data and run anomaly checks | | Integrations | Manage HTTP connectors, connector actions, auth accounts, and automation flows | | Operations | Diagnose environment issues, manage login state, configure sharing, upload CDN assets |

From Request to Delivery

  1. Describe the goal. Explain who will use the app, what they need to do, and any existing app or reference material.
  2. Share requirements and preferences. Add the business details and appearance preferences that matter to you. Include any colors, websites, or design images you like.
  3. Choose how to build. Fast suits clear requirements you want to move forward with. Plan produces a reviewable proposal and waits for approval before creating resources.
  4. Review and refine. Check the delivered app and common actions, then request page, field, or workflow changes. Your chosen style is reused unless you ask to change it.
For existing-app improvements, review the visual proposal before backups and implementation. Restoration uses the saved backups; page content and theme settings need separate backups, with the supported scope explained before changes.

Recent Updates

2026.10.14-1 improves appearance customization, with better support for style descriptions, reference websites, and design images. Read the full changelog.

Quick Start

1. Install

npm install -g openyida

OpenYida requires Node.js 18 or later. The package exposes both openyida and yida commands.

Global npm installations check for updates at most once a day and update automatically. Run openyida update to update manually, or set OPENYIDA_NO_AUTO_UPDATE=1 to keep your installed version.

Auto-update environments and development settings

Local terminals and AI tools share the same policy. Managed cloud Agent runtimes skip automatic updates. For development and testing, OPENYIDA_AUTO_UPDATE_SECS changes the check interval.

If Codex is already installed, OpenYida also imports a local Codex plugin during postinstall. Restart Codex after installation, then type @宜搭 or @openyida in the composer to attach the OpenYida context.

Optional: install DingTalk Workspace CLI (dws)

For DingTalk A2UI authoring, validation or messaging, install the official DingTalk Workspace CLI separately:

npm install -g dingtalk-workspace-cli
dws version

Alternatively, openyida dws install displays official instructions for your platform without running an installer. Once installed, openyida dws [args] forwards arguments to your system's dws.

OpenYida does not bundle or automatically install dws. Dws manages its own version, updates and login. Use dws auth login for DingTalk business access and verify the target organization and account. Native YiDA cool-card creation, reads and editor links work without dws; updating a chat card through dws does not save a YiDA template. See the official instructions for other installation methods and check the installed command's --help before use.

2. Check Your Environment

Run this from the AI coding workspace where you want OpenYida to operate:

openyida agent-capabilities --summary-json

This checks the installed version, login state, and workspace. Use openyida agent-capabilities --json for detailed diagnostics.

3. Log In

openyida login

OpenYida login defaults to OAuth token mode. It opens the DingTalk OAuth authorization page, receives the local loopback callback, exchanges code / authCode with the Yida server, and stores access_token / refresh_token in the user auth store when available. If the user auth store is not writable, OpenYida explicitly falls back to the current project cache and reports that narrower persistence scope.

When the user names a target Yida entry URL, pass it to the login command so OpenYida can select the matching environment and auth profile. For example:

openyida login https://yida-group.alibaba-inc.com/
openyida login --alibaba

For token status checks, use:

openyida login --check-only --json
openyida auth status

In QwenWork web, openyida login --qwenwork --json (or ordinary login) reuses the sandbox identity. If cloud authentication fails, the same command falls back to OAuth; follow its authorization URL and wait for completion. Desktop and other hosts keep their existing login paths.

Login supports DingTalk and QwenWork organizations, ODS and personal spaces. Sessions preserve server-provided identity classification, business domain and refresh credentials. Historical sessions default to DingTalk organizations. JSON errors include bounded diagnostics without credentials or response bodies.

For QwenWork cloud identity diagnostics, run inside the actual cloud execution environment:

openyida auth qwenwork user-info --json

This legacy diagnostic reads the injected host credentials and reports identity and scope without printing or saving tokens. It does not create or validate a YiDA session; QwenWork organization IDs are not YiDA corpId values. Check the YiDA session with openyida login --check-only --json. See QwenWork login guidance for authentication behavior.

To reuse an existing shared login profile for the current project, switch first instead of starting a new OAuth login:

openyida auth profiles
openyida auth profile switch <auth_profile>

If the target profile is not listed, run openyida login to add it, then switch to the new profile.

OpenYida does not install Playwright by default.

4. Build With an AI Agent

Ask your coding agent for a concrete Yida application or workflow:

Create a CRM application in Yida with customer, contact, opportunity, and follow-up forms.
Use Plan mode to design a procurement app with requests, approvals, and dashboards. Show me the proposal first.
Build an oolong tea brand storefront and order management back office, with navigation groups.
Improve the appearance of this existing app. Show me the proposal first, preserve business logic, and back up before changes.

The agent can then call OpenYida commands to create the application, generate source files, publish pages, and return the final Yida URLs. In Codex, QwenWork, Qoder, Qoder IDE, and QoderWork environments, successful creation and publish commands also include a browser handoff so the agent can open the resulting Yida page in the in-app browser. Use --open to force this handoff or --no-open to suppress it.

Supported AI Coding Tools

| Tool | Support | |------|---------| | Codex | Full support | | Claude Code | Full support | | MuleRun | Full support | | Cursor | Full support | | Visual Studio Code | Full support | | QwenWork(千问办公) | Full support | | Qoder | Full support | | Qoder IDE | Full support | | QoderWork | Full support |

How It Works

flowchart LR
  A["AI coding agent"] --> B["OpenYida CLI"]
  B --> C["Environment detection"]
  B --> D["Login and organization context"]
  B --> E["Yida API operations"]
  B --> F["Local page generation and compile"]
  E --> G["Native Yida app"]
  F --> G

OpenYida keeps platform-specific behavior inside the CLI, while agents interact with predictable commands and project files.

Project Layout

openyida/
├── bin/yida.js                 # CLI entry and command routing
├── lib/
│   ├── app/                    # Application, form, page, import/export commands
│   ├── auth/                   # OAuth token login, token session storage, organization switch
│   ├── connector/              # HTTP connector lifecycle and smart creation
│   ├── core/                   # Environment detection, i18n, diagnostics, data commands
│   ├── process/                # Process form creation, configuration, preview
│   ├── report/                 # Yida report and chart generation
│   └── samples/                # Reusable code templates emitted by openyida sample
├── project/                    # Default workspace template for generated Yida projects
├── yida-skills/                # Source skill docs and Yida API references
└── scripts/                    # CI, packaging, and installation helpers

Capabilities

For a user-facing list of supported features and matching CLI commands, see OpenYida 功能完整列表.

Application and Form Management

openyida create-app --name "CRM" --desc "Customer management"
openyida sample yida-design app-theme --output .cache/openyida/crm/app-theme.css
openyida create-app --name "CRM" --desc "Customer management" --theme-file .cache/openyida/crm/app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
openyida app-list --type managed --page 1 --size 16
openyida update-app APP_XXX --theme-file .cache/openyida/crm/app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
openyida corp-efficiency
openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
openyida create-form update APP_XXX FORM_XXX .cache/openyida/forms/customer-changes.json
openyida create-form update APP_XXX FORM_XXX --data-file .cache/openyida/forms/customer-changes.json
openyida create-form resume APP_XXX FORM_XXX .cache/openyida/forms/customer-fields.json --json
openyida sample openyida-page-template form-fields --output .cache/openyida/forms/customer-fields.json
openyida sample openyida-page-template canvas-form-drawer --output project/pages/src/customer-entry.canvas.jsx --var APP_TYPE=APP_XXX --var FORM_UUID=FORM_XXX
openyida get-schema APP_XXX FORM_XXX
openyida get-schema APP_XXX FORM_XXX --compact --resolve-fields "Customer Name,Status"
openyida get-schema APP_XXX --all --output-dir .cache/schemas

Native forms use a component tree: business fields capture data, tabs switch content, action groups trigger business actions, images establish visual focus, status areas provide feedback, and Divider plus ColumnContainer organize hierarchy and columns. create-form field definitions create 19 business field types plus Divider and ColumnContainer (mapped to ColumnsLayout + Column), while updates retain other existing components.

Custom Page Development

openyida create-page APP_XXX "Dashboard" --mode dashboard
openyida publish pages/src/home.canvas.jsx APP_XXX FORM_XXX

Custom page source is authored directly from PRD and design guidance. Custom page development uses yida-canvas-custom-page and .canvas.jsx.

For member, department, attachment, and image upload components, .canvas.jsx pages should follow yida-canvas-custom-page/references/native-components-bridge.md for feature detection, fallback, and value normalization.

For the current custom page native-component support matrix, see Yida native components supported by custom pages.

Workflow, Data, and Permissions

openyida create-process APP_XXX "Purchase Request" .cache/openyida/process/fields.json .cache/openyida/process/process.json
openyida configure-process APP_XXX FORM_XXX .cache/openyida/process/process.json
openyida process preview APP_XXX PROC_INST_XXX --output .cache/openyida/process/process.html
openyida data query form APP_XXX FORM_XXX --page 1 --size 20
openyida data query form APP_XXX FORM_XXX --dynamic-order '{"dateField_xxx":"-"}'
openyida data query process APP_XXX FORM_XXX --current-approver USER_ID --count
openyida data create form APP_XXX FORM_XXX --expect-form-name 客户 --expect-form-type receipt --data-file .cache/openyida/data-import/record.json
openyida get-permission APP_XXX FORM_XXX

--current-approver takes an exact userId, not a display name. It scans running instances and their current TODO records, counts each matching instance once, and respects the current login's server-authorized data scope. Omit --count to include matching instance data. An incomplete scan or permission failure is reported as an error; the result does not claim application-wide visibility.

Append and forward permissions are configured in the process definition through nodes[].actions.normalActions/appendActions. Both create-process and configure-process compile these settings into matching designer and runtime properties. See approval action configuration for the JSON example, defaults, and verification limits.

configure-process 的流程 JSON 中,审批人可配置为发起人、指定成员、指定角色、部门主管或直属主管,例如:

{
  "nodes": [
    {
      "type": "approval",
      "name": "主管审批",
      "approver": {
        "type": "user",
        "users": [{ "id": "manager7350", "name": "九神" }],
        "multiApproverType": "all"
      }
    }
  ]
}

configure-process 会先通过表单绑定和流程版本只读接口证明 processCode ownership。目标已有 PUBLISHED 流程或 SAVED 草稿时,整图替换必须在人工确认后显式传入 --replace;未确认或 ownership 不匹配时远程写入数为 0。draft/save/publish 均为 one-shot,认证异常返回 NON_IDEMPOTENT_RESULT_UNKNOWN,不得自动重试。

发布成功还必须精确回读同一 PUBLISHED processId/processVersion 的 getProcessById 平台视图,并验证可见节点的组件、名称、顺序和审批模式。只有输出 verificationLevel: "PLATFORM_VIEW_VERIFIED" 才表示平台 view 已验证;PUBLISHED_UNVERIFIED 表示发布可能已生效但回读不完整,不能宣称 processJson 已验证,也不能直接重放写请求。

When creating or updating test data with openyida data, Yida date fields must use 13-digit millisecond timestamps, for example "dateField_xxx": 1719705600000. Do not submit YYYY-MM-DD strings for DateField or CascadeDateField values. For deterministic query order, pass --dynamic-order '{"fieldId":"+"}' for ascending order or --dynamic-order '{"fieldId":"-"}' for descending order. Without --dynamic-order, searchFormDatas does not guarantee a stable result order; pagination, comparison, and pairing logic must not depend on the default order. Temporary JSON, CSV, and one-off import scripts should live under .cache/openyida/ so generated run artifacts do not clutter the repository root.

Real Environment E2E

Most checks should stay offline, but OpenYida also includes an explicit real-environment smoke path for release and nightly validation:

OPENYIDA_E2E=1 npm run test:e2e:real
OPENYIDA_E2E=1 npm run test:e2e:real:full
OPENYIDA_E2E=1 OPENYIDA_E2E_FULL_STAGES=auth,app,form,process npm run test:e2e:real:full
npm run test:e2e:real:skills

The runner creates a disposable app, form, and custom page with an OY_E2E_* prefix, then verifies token login, app listing, schema fetch, data query, and page publish. It writes a registry to project/.cache/e2e-real/ so created resources can be audited later. Run openyida login for the target environment before starting the real E2E runner.

test:e2e:real:full extends the smoke path into a broad deterministic feature matrix: auth/env, app update, form update and option mutation, page build/compile/generate/publish, data create/get/update/query, permission read, page config and short URL check, report create/append, dashboard skill verification, export/import, batch, task-center, formula/doctor/sample/CDN config, and local connector parsing/template generation. AI-backed commands such as flash-to-prd are available as the optional ai stage because they depend on remote model availability. Workflow mutation is available as the opt-in process stage; it creates and republishes a workflow on the disposable E2E form and records advanced official-node fixtures for review.

test:e2e:real:skills enforces coverage for every directory under yida-skills/skills/. Each skill must be classified as real E2E, offline/unit, opt-in, or deprecated with an explicit reason. This prevents new skills from quietly bypassing the real-environment test plan.

Each successful full run leaves a human-inspectable result app in the target organization. The final step publishes a dedicated Full E2E Dashboard custom page, renames the app to OY_E2E_*_PASSED by default, and prints direct links for the app, form, dashboard page, and report; the same links are saved under resultApp in the registry JSON.

Useful options:

| Env var | Purpose | |---------|---------| | OPENYIDA_E2E_PREFIX | Override the disposable resource name prefix | | OPENYIDA_E2E_CORP_ID | Switch to the dedicated test organization before creating resources | | OPENYIDA_E2E_RESULT_APP_NAME | Override the final app name shown as the full-run result | | OPENYIDA_E2E_BASE_URL | Override the Yida base URL for private deployments | | OPENYIDA_E2E_FIELDS_FILE | Use a custom form fields fixture | | OPENYIDA_E2E_PAGE_SOURCE | Use a custom page source for publish verification | | OPENYIDA_E2E_SKIP_PUBLISH=1 | Skip custom page creation and publish | | OPENYIDA_E2E_REGISTRY_DIR | Write registries outside project/.cache/e2e-real/ | | OPENYIDA_E2E_FULL_STAGES | Comma-separated stage list for test:e2e:real:full; use all or omit for the default broad matrix |

Use npm run test:e2e:real:cleanup to list recorded disposable resources. OpenYida does not yet expose a safe app/form deletion command, so cleanup is intentionally a registry-backed audit step rather than an automatic destructive action.

Skill Evaluation Harness

The E2E paths above prove the CLI works. The eval harness under scripts/eval/ measures whether the agent still routes natural-language requests to the right sub-skill and produces good output after you edit yida-skills/SKILL.md. It is the feedback loop for harness engineering.

# Routing eval — "did it pick the right sub-skill?" No real resources, no login.

Drives a headless claude -p agent with each scenario prompt and checks

the selected sub-skill against the golden set.

npm run eval:routing

Doc quality — static analysis of all SKILL.md files (standards + maintainability).

npm run eval:doc-quality

Safety — credential leak detection, command whitelist, corpId consistency.

npm run eval:safety

Coverage — how much of the skill/category/reference space is covered by scenarios.

npm run eval:coverage

Comprehensive — 10-dimension scorecard for a single skill (doc quality, routing,

safety, step completeness, output validity, efficiency, stability, coverage).

Outputs a JSON scorecard, SVG radar chart, and trend analysis.

npm run eval:comprehensive -- --skill yida-dashboard

Tool-pipeline baseline (end-to-end) — wraps the real-environment runner for one

sub-skill, adds guardrail assertions, screenshots the published page, and writes a

human scoring template. Runs deterministic CLI commands (no agent) so it serves as

a control that proves the build→publish→screenshot→score plumbing itself works.

Requires OPENYIDA_E2E=1 and a valid token session.

OPENYIDA_E2E=1 npm run eval:e2e -- --skill yida-dashboard --screenshot

Same, plus automatic screenshot scoring via the local multimodal claude -p.

OPENYIDA_E2E=1 npm run eval:e2e -- --skill yida-dashboard --screenshot --auto-score

Real-generation eval — "can one sentence really build a usable app?" Feeds a

natural-language request ("帮我创建一个订单管理系统") to a headless claude -p

that actually reads the openyida skill and runs the CLI to build a real app, then

reuses the screenshot + scoring + report pipeline. Creates real 宜搭 resources, so

it needs OPENYIDA_E2E=1 + an authenticated agent. Unlike the deterministic baseline

above, here the agent self-orchestrates.

OPENYIDA_E2E=1 npm run eval:generate -- --screenshot

Re-evaluate an archived real CLI trace against the latest scenario contract and

current read-only platform state. This does not run an agent or mutate resources.

npm run eval:replay -- --report <generation-report.json> --scenario <scenario.json> --app-type APP_XXX

Run all three in one pass (routing + tool-pipeline baseline + real generation).

OPENYIDA_E2E=1 npm run eval:all -- --skill yida-dashboard --screenshot

Optional: a zero-dependency local web console (buttons + live streamed output)

that runs the tasks above on click. Binds to 127.0.0.1 only; tasks are a fixed

whitelist. Use the Node version you start it with, so run nvm use 20 first.

npm run eval:dashboard # http://127.0.0.1:4500

Configuration precedence is CLI flag > env (OPENYIDA_EVAL_*) > scripts/eval/eval.config.json > defaults.

| Flag | Purpose | |------|---------| | --mode e2e\|routing\|generate\|doc-quality\|safety\|coverage\|comprehensive\|all | Which evals to run (default e2e) | | --skill | Restrict the e2e run to one sub-skill; stages are reverse-looked-up from the SKILL_COVERAGE matrix | | --stages a,b | Explicit stage list, overriding the skill reverse-lookup | | --runs N | Multi-run stability: repeat routing N times, report consistency rate | | --screenshot / --no-screenshot | Capture the published page (default on; skipped gracefully if Playwright is absent) | | --auto-score / --no-auto-score | Score screenshots with the local claude -p agent (default off → human template only) | | --scenarios

| Routing golden-set directory (default scripts/eval/scenarios) | | --gen-scenarios | Real-generation golden-set directory (default scripts/eval/scenarios/generation) |

Results are written back into the existing acceptance-manifest.json under a new eval section (guardrails, screenshots, scores) plus two sibling artifacts: a scoring.md human-scoring template and a self-contained eval-report.html visual report (screenshots inlined as base64, so the single file opens anywhere). Routing results land in project/.cache/eval/routing-report.json; generation results (with their own scoring.md + eval-report.html) land in project/.cache/eval/generate/gen-/. The harness degrades gracefully: a missing Playwright skips screenshots (enable with npm install --no-save playwright && npx playwright install chromium), and a missing claude CLI skips auto-scoring while keeping the human template. A guardrail failure (for example a resource-creating command issued before a successful login --check-only) fails the run.

A zero-dependency local dashboard (npm run eval:dashboard, then open http://127.0.0.1:4500) drives these runs from buttons and streams output live; its "📊 查看最新报告" button opens the most recent eval-report.html.

Connectors, Integrations, and Reports

Automation spec sendMessage nodes accept toRoles: [{"roleCode":"REAL_ROLE_ID","roleName":"Finance"}] for NORMAL messages, alongside users and member fields. Process approval, operator, and CC nodes can also select roles. Role selection differs from conditional actor selection, which does not yet have a complete CLI shorthand. See the yida-flow-node-config skill for shared node guidance.

openyida connector smart-create --curl "curl https://api.example.com/users"
openyida connector list
openyida integration create APP_XXX FORM_XXX "Sync customer data"
openyida integration create APP_XXX FORM_XXX "Approval result notify" \
  --events processFinish --approval-actions agree,disagree --receivers 123456

Capability probe only; currently fails closed before auth/spec reads/remote writes

openyida integration update APP_XXX FORM_XXX LPROC_XXX \ --spec .cache/openyida/integration/desired-spec.json openyida create-report APP_XXX "Sales Dashboard" .cache/openyida/reports/charts.json openyida append-chart APP_XXX REPORT_XXX .cache/openyida/reports/chart.json

Aggregate tables use two independent optimistic-concurrency axes.

openyida aggregate-table inspect APP_XXX FORM_XXX --json openyida aggregate-table preview APP_XXX FORM_XXX .cache/openyida/aggregate/design.json --json openyida aggregate-table save APP_XXX FORM_XXX .cache/openyida/aggregate/design.json --json --no-open openyida aggregate-table publish APP_XXX FORM_XXX .cache/openyida/aggregate/design.json --json --no-open openyida aggregate-table status APP_XXX FORM_XXX --json

Aggregate-table save verifies the draft stashGmtModified axis; publish verifies the live gmtModified axis. Both commands require the corresponding GET readback revision to advance; when a response revision exists it must equal that readback axis. Both commands also require canonical readback of relationForms, relationships, aggregatedFields, auxFields, formulaFields, and validators. The CLI intentionally does not expose aggregate-table deletion, AI authoring, a high-level design DSL, or tenant-dynamic limits until the corresponding platform contracts are proven.

The opt-in aggregate real-E2E runner additionally requires OPENYIDA_AGGREGATE_E2E_RUN_ID and an exact OPENYIDA_AGGREGATE_E2E_OWNED_MARKER beginning with __. Before its first write it verifies the exact list/inspect identity and name, persists a redacted manifest, registry, and baseline snapshot, then conditionally restores the baseline using the latest live revision. Missing ownership proof or concurrent revision movement produces PLATFORM_PROBE_REQUIRED / restore_blocked without a restore write.

CLI Reference

Run openyida --help or openyida --help for detailed usage.

Report a problem or contribute a fix

Tell your AI: “OpenYida keeps failing. Analyze the cause and prepare feedback.” You can prepare a redacted report and reproduction steps without a GitHub account or a local source checkout. Preparing a report does not submit it.

openyida feedback report --input summary.json --output-dir feedback-draft-001

Your AI prepares the summary from observed evidence and selected reproduction screenshots. PRs include before/after test comparisons. Before anything is published, review the final text and images, confirm sensitive information, and remove anything you do not want to share. See the report input format. Review business information before sharing. With a GitHub account, authorize your AI to submit an Issue or contribute a verified public OpenYida fix through your personal Fork. Without an account, create a GitHub account first. If that is not possible, share the report in the AI开发者群 (AI developer group).

To configure a submission tool, your AI can first run openyida feedback github setup --json to check gh and the current account. After you approve the installation plan, it can run openyida feedback github setup --install --confirm --json, using existing Homebrew on macOS/Linux or WinGet on Windows. Without a supported manager, official instructions or the web remain available. Browser authorization is a separate step performed by you; installation does not log you in, request passwords or tokens, or replace public-content approval. Resume the original draft after setup. See the account and tool guide.

When preparing a PR that needs a personal Fork, or when you ask how to support the project, the feedback Skill can inspect account support state with openyida feedback github status --json. Star and a personal Fork are optional: feedback github star --confirm and feedback github fork --confirm run only after you choose them. Existing actions are reused. This check is separate from installation and automatic updates; neither action is required to submit an Issue.

Auth & Environment

| Command | Description | |---------|-------------| | openyida login [--qwenwork] [target-url] [--env \|--intl\|--overseas\|--global\|--yidaapps\|--alibaba] [--client-id ] [--endpoint ] [--no-browser] | Login with OAuth token mode | | openyida logout | Logout / unbind current project auth | | openyida auth [--json] | Token login state and profile management | | openyida org [--json] [--corp-id ] | Organization management (list / switch existing profiles first) | | openyida env [--json\|setup\|list\|show\|switch\|add\|remove] [options] | Detect AI tool environment & token login state |

App Management

| Command | Description | |---------|-------------| | openyida app-settings [--section theme\|basic\|data-card\|cool-card] [--json] | Read theme, frontend/backend, native data card and cool card management links | | openyida cool-card [cardPageCode] [--type im\|onebox] [--name ] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]</code> | Create and read native YiDA cool cards (interactive and pinned) | | <code>openyida data-card <list\|create\|get\|save\|publish\|probe\|release-get\|release-save\|release-check\|release-test\|refs> <appType> [cardCode] [--name <title>] [--description <text>] [--schema <file>\|--config <file>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--published\|--version <version>] [--page <n>] [--size <n>] [--search <text>] [--allow-send] [--json]</code> | Create, configure, publish and distribute native YiDA data cards | | <code>openyida plugin <dev-list\|get\|version-list\|version-get\|extensions\|installed-list\|installed-get\|runtime-list\|probe\|create\|create-version\|save-dev\|configure\|enable\|disable\|upgrade\|uninstall> [pluginId] [version] [options]</code> | Manage YiDA platform plugin versions, installation state, and runtime registrations | | <code>openyida app-entry get <appType> [--json]</code> | Read frontend and management runtime entries | | <code>openyida app-entry set <appType> [--frontend <url>\|--frontend-page <formUuid>] [--management <url>\|--management-page <formUuid>] [--clear-frontend] [--clear-management] [--json]</code> | Register application entries with partial update and readback | | <code>openyida app-list [--type managed\|created] [--page N] [--size N]</code> | Page through apps I manage or created | | <code>openyida solution-template <search\|detail\|prepare\|ai-build\|enable\|status> [templateId\|requestId] [options] [--json]</code> | Solution templates for independent local agents: discovery, cloud AI handoff and direct enable | | <code>openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]</code> | Query enterprise efficiency overview and detail reports | | <code>openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]</code> | Create a Yida app | | <code>openyida design-plan catalog [--json]</code> | List available planning themes and page patterns | | <code>openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]</code> | Initialize a plan draft from confirmed requirements | | <code>openyida design-plan preview <build-plan.json> --part-file <module.json> [--json]</code> | Update plan drafts by module | | <code>openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--rebase-parts] [--check] [--json]</code> | Generate or update plan documents and theme with consistency checks | | <code>openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]</code> | Patch plan fields and optionally sync documents and theme | | <code>openyida update-app <appType> [--name "..."] [--desc "..."] [--icon <name>] [--icon-color <color>] [--colour <key>] [--theme-color <color>] [--theme-file <css>] [--confirm-legacy-app-style] [--disable-custom-theme] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]</code> | Update app info | | <code>openyida data-manage-style guide [--json]</code> | Inspect data-management UI directions, tokens and scoped CSS guidance | | <code>openyida data-manage-style list [--json]</code> | List optional data-management recipe examples | | <code>openyida data-manage-style generate --app <appType> --form <formUuid> --style <id> --base-css <theme.css> --output <new.css> [--focus balanced\|actions] [--field-id <actualId>]... [--emphasize-field <actualId>]... [--tag-field <selectFieldId>]... [--follow-shell] [--json]</code> | Generate data-management CSS from an optional recipe (local only) | | <code>openyida app-theme snapshot <appType> --output-dir <new-dir> [--json]</code> | Archive application theme settings and CSS (read-only) | | <code>openyida app-theme restore <appType> --snapshot <before.json> --expected-current <after.json> [--check \| --confirm --output-dir <new-dir>] [--json]</code> | Check or restore an application theme snapshot (supported exact values only) | | <code>openyida upgrade-app-theme <appType> --explicit-request [--confirm \| --prepare --output-dir <dir>] [--json]</code> | Upgrade an AI-built legacy app theme (explicit request and ask_human confirmation required) | | <code>openyida app-online <appType> [--to-ding-app-center] [--show-app-center]</code> | Enable a Yida app | | <code>openyida app-offline <appType> [--to-ding-app-center] [--show-app-center]</code> | Disable a Yida app | | <code>openyida nav-group <list\|create\|rename\|delete\|move\|order\|auto-order\|hide\|show> <appType> ...</code> | Manage app sidebar navigation groups | | <code>openyida app-permission <get\|set\|add\|remove\|search-user> ...</code> | Manage app primary, data, and developer admins | | <code>openyida i18n <overview\|config\|languages\|list\|upsert\|delete\|translate\|translate-all\|upgrade> <appType> ...</code> | Manage app multilingual copy and language config | | <code>openyida export <appType> [output]</code> | Export app (generate migration package) | | <code>openyida import <file> [name]</code> | Import migration package, rebuild app |</p> <h3>Forms & Pages</h3> <p>| Command | Description | |---------|-------------| | <code>openyida create-form batch <appType> <plan.json> [--concurrency 1..4] [--check] [--json]</code> | Create forms concurrently by dependency | | <code>openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--layout single\|double\|card\|section] [--theme default\|compact\|comfortable] [--label-align top\|left\|right] [--icon auto\|<iconName>] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]</code> | Create a form page | | <code>openyida create-form icons [--json]</code> | List available form navigation icons | | <code>openyida create-form validate-fields <fieldsJsonOrFile> [--json]</code> | Validate form field JSON locally | | <code>openyida create-form update <appType> <formUuid> (<changesJsonOrFile> \| --data-file <changesJsonOrFile>) [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]</code> | Update a form page | | <code>openyida create-form resume <appType> <formUuid> <fieldsJsonOrFile> [--json]</code> | Update a form page | | <code>openyida create-form patch <appType> <formUuid> <patchJsonOrFile> [--open\|--no-open]</code> | Update a form page | | <code>openyida create-form rule <appType> <formUuid> <rulesJsonOrFile> [--open\|--no-open]</code> | Update a form page | | <code>openyida create-form validation <appType> <formUuid> <validationsJsonOrFile> [--open\|--no-open]</code> | Update a form page | | <code>openyida add-validation <appType> <formUuid> --field <labelOrId> --type <phone\|regex\|idCard\|email\|...> [--message <text>]</code> | Update a form page | | <code>openyida create-form bind-datasource <appType> <formUuid> <fieldLabelOrId> <dataSourceJsonOrFile> [--open\|--no-open]</code> | Update a form page | | <code>openyida create-form add-option <appType> <formUuid> <fieldLabel> <option1> [option2] ...</code> | Update a form page | | <code>openyida list-forms <appType> [--keyword <text>]</code> | List forms/pages in an app | | <code>openyida aggregate-table <list\|create-empty\|inspect\|preview\|save\|publish\|status> <appType> ...</code> | Manage aggregate tables (virtualView) | | <code>openyida print-template <list\|inspect\|fields\|build\|create\|copy\|save\|restore\|settings\|status\|order\|render\|files> <appType> <formUuid> [templateId] [--config <file>] [--scene <order\|quotation\|delivery\|receipt\|approval\|codes>] [--bindings <file>] [--name <title>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--status START\|STOP] [--instance <id>] [--page <n>] [--size <n>] [--allow-permission-change] [--json] \| print-template <list\|render> --detail-url <url> [--template-id <id>] [--json]</code> | Manage native YiDA print templates, generate document layouts, back up, restore and request PDFs | | <code>openyida form-view <list\|inspect\|save\|create\|order> <appType> <formUuid> [viewUuid] [--config <file>] [--name <title>] [--with-config] [--expected-hash <hash>] [--backup <file>] [--replace] [--json]</code> | Native form views: list, inspect and configure columns, widths, frozen columns and filters | | <code>openyida custom-button <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [buttonUuid] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]</code> | Native custom buttons: inspect and configure form edits, automation flows and page links | | <code>openyida form-message <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [messageId] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]</code> | Native form notifications: inspect triggers, recipients and templates, then safely maintain bindings | | <code>openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]</code> | Get one form Schema or all form Schemas | | <code>openyida check-prd-completeness <prd.md> --app-type <appType> [--build-manifest <file>] [--json]</code> | Check resources, frontend menu hiding and application theme delivery | | <code>openyida check-design <design.md> [--prd <prd.md>] [--base-dir <dir>] [--json]</code> | Validate design documents, theme tokens and PRD handoff | | <code>openyida er <appType> [--format mermaid\|json] [--output file] [--include-system] [--include-pages]</code> | Export app entity relationship diagram | | <code>openyida create-page <appType> "<name>" [--mode dashboard] [--hide-nav] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]</code> | Create a custom display page | | <code>openyida build-page <sourceFile> [--output file\|--write]</code> | Build Yida-compatible page source | | <code>openyida check-page <src> [--compat] [--json]</code> | Check custom page standards | | <code>openyida compile <src> [--canvas] [--compat] [--skip-lint] [--strict-theme\|--allow-fixed-brand] [--json]</code> | Compile custom page locally | | <code>openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--compat] [--skip-lint] [--fix-theme] [--strict-theme] [--allow-fixed-brand] [--auto-nav-order] [--open\|--no-open] [--json]</code> | Compile and publish custom page | | <code>openyida update-form-config <appType> <formUuid> <true\|false\|keep> "<title>" [--locale zh_CN\|en_US\|ja_JP]</code> | Update form configuration | | <code>openyida get-form-config <appType> <formUuid> [--json]</code> | Query form configuration |</p> <h3>Data & Permissions</h3> <p>| Command | Description | |---------|-------------| | <code>openyida data <query\|get\|create\|update\|transfer> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --expect-form-name <name> --expect-form-type receipt --confirm [--json]</code> | Unified data management (form/process/task/subform) | | <code>openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|link\|scaffold\|options\|inspect\|count> [--spec <file.json> --output <file.canvas.jsx>] [options]</code> | Global task center (todo/processed/cc etc.) | | <code>openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain></code> | Query organization basic info, capacity, quotas, and domain settings | | <code>openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]</code> | Fetch Markdown content from a DingTalk document | | <code>openyida read-dingtalk-tingji <taskUuid> [--json]</code> | Fetch DingTalk Tingji details by task UUID | | <code>openyida get-permission <appType> <formUuid> [--package-uuid <packageUuid>] [--package-type <FORM_PACKAGE_VIEW\|FORM_PACKAGE_START>] [--check-matrix] [--json]</code> | Query form permission config | | <code>openyida save-permission <appType> <formUuid> [--package-type <FORM_PACKAGE_VIEW\|FORM_PACKAGE_START>] [--package-uuid <packageUuid>\|--create --name <name>] [--data-permission <json>] [--action-permission <json>] [--field-permission <json>] [--members <userIds>\|--roles <roleIds>\|--all-members\|--matrix <json>] [--confirm-member-replace] [--conditions-file <conditions.json>] [--owner-field <fieldId>] [--field-access <fieldId:view\|edit\|hidden\|encrypt>] [--field-default <access>] [--dry-run]</code> | Save form permission config | | <code>openyida permission-manage <copy\|rename\|delete> <appType> <formUuid> <packageUuid> [--name <name>] [--package-type <type>] [--dry-run] [--confirm]</code> | Copy, rename or delete a permission group | | <code>openyida matrix <list\|detail\|references\|rows\|create\|update\|delete\|upsert-rows\|delete-rows> [matrixId] [--file config.json] [--page 1] [--limit 20] [--expect hash] [--dry-run] [--confirm]</code> | Manage permission matrix definitions, references and rows | | <code>openyida corp-manager <search-user\|list\|add\|remove\|address-book> ...</code> | Manage platform admins and address book permissions | | <code>openyida agent-center <list\|create\|update\|cancel\|range\|search-user> ...</code> | Manage process and departure delegation |</p> <h3>Process</h3> <p>| Command | Description | |---------|-------------| | <code>openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]</code> | Configure and publish process rules; Supports append/forward via JSON nodes[].actions.normalActions/appendActions | | <code>openyida create-process <appType> <formTitle> <fieldsJsonFile> <processDefinitionFile> [--replace] \| create-process <appType> --formUuid <formUuid> <processDefinitionFile> [--replace]</code> | Create process form (all-in-one); Supports append/forward via JSON nodes[].actions.normalActions/appendActions | | <code>openyida ai-form-setting <get\|fields\|models\|enable\|disable\|save> <appType> ...</code> | Manage process form AI approval prompts | | <code>openyida process preview <appType> ...</code> | Preview process instance (visual flowchart) | | <code>openyida process fields <appType> <formUuid> [--json]</code> | Check process designer field loading and subtable field paths |</p> <h3>Page Config & Sharing</h3> <p>| Command | Description | |---------|-------------| | <code>openyida verify-short-url <appType> ...</code> | Verify short URL | | <code>openyida save-share-config <appType> ...</code> | Save public access / share config | | <code>openyida get-page-config <appType> <formUuid></code> | Query page public access config | | <code>openyida externalize-form <appType> <formUuid> [--schema-file file]</code> | Plan external access-safe mirror fields |</p> <h3>Reports</h3> <p>| Command | Description | |---------|-------------| | <code>openyida create-report <appType> "<name>" ... [--json] [--open\|--no-open]</code> | Create a Yida report | | <code>openyida append-chart <appType> <reportId> ... [--json] [--open\|--no-open]</code> | Append chart to existing report | | <code>openyida report inspect <appType> <reportId> --json</code> | Inspect report runtime bindings (read-only) | | <code>openyida report configure <appType> <reportId> <cid> --hide-zero-values <true\|false> [--hide-zero-categories <true\|false>] [--hide-zero-legends <true\|false>] [--dry-run] [--json]</code> | Configure dynamic zero-value hiding on a native bar chart (explicit opt-in) |</p> <h3>Connectors</h3> <p>| Command | Description | |---------|-------------| | <code>openyida connector list</code> | List HTTP connectors | | <code>openyida connector create "name" "domain" ...</code> | Create a connector | | <code>openyida connector detail <id></code> | View connector details | | <code>openyida connector delete <id> [--force]</code> | Show manual deletion guidance (CLI does not delete) | | <code>openyida connector add-action --operations <file> --connector-id <id></code> | Add an action | | <code>openyida connector update-action --connector-id <id> --action <operationId> --query-json JSON --confirm</code> | Safely update action query defaults | | <code>openyida connector list-actions <id></code> | List actions | | <code>openyida connector delete-action <id> <operation-id></code> | Delete an action | | `openyi</p> <p>... <em>(README truncated for length)</em></p> </article> <!-- Article Completion Section --> <div class="reader-footer mt-5 pt-5"> <div class="reader-completion-box rounded-3 p-4 text-center"> <div class="reader-completion-icon mb-3"> <i class="fas fa-check-circle"></i> </div> <p class="reader-completion-text fw-semibold mb-1" style="font-size: 1rem;">You finished the article!</p> <p class="reader-completion-subtext small mb-4">Want to go deeper or explore more stories?</p> <div class="d-flex justify-content-center gap-3 flex-wrap"> <a href="https://github.com/openyida/openyida" target="_blank" rel="noopener noreferrer" class="btn btn-primary-custom btn-sm rounded-pill px-4"> <i class="fas fa-external-link-alt me-2"></i>Full Article on GitHub Trending </a> <a href="https://neshdevtech.com/news" class="reader-back-btn btn btn-sm rounded-pill px-4"> <i class="fas fa-newspaper me-2"></i>More Tech News </a> </div> </div> </div> <!-- Internal Linking CTA --> </div> </div> </div> <!-- Sidebar (desktop only) --> <div class="col-lg-4 col-xl-3 d-none d-lg-block"> <div class="sticky-top" style="top: 80px;"> <!-- Mini Controller Widget --> <div class="card border-0 shadow-sm mb-4 bg-surface"> <div class="card-body p-4"> <h6 class="fw-bold mb-3"><i class="fas fa-glasses me-2 text-primary-custom"></i>Reading Mode</h6> <p class="text-secondary-custom small mb-4">Adjust typography and theme to your preference.</p> <div class="mb-3"> <label class="text-muted-custom small d-block mb-2">Typography</label> <div class="d-flex gap-2"> <button class="btn btn-light btn-sm flex-grow-1 border-custom" id="sidebar-font-serif">Serif</button> <button class="btn btn-light btn-sm flex-grow-1 border-custom" id="sidebar-font-sans">Sans</button> </div> </div> <div class="mb-4"> <label class="text-muted-custom small d-block mb-2">Text Scale</label> <div class="d-flex gap-2"> <button class="btn btn-light btn-sm flex-grow-1 border-custom" id="sidebar-size-dec"><i class="fas fa-minus small"></i></button> <button class="btn btn-light btn-sm flex-grow-1 border-custom" id="sidebar-size-inc"><i class="fas fa-plus small"></i></button> </div> </div> <div class="border-top border-light-custom pt-3"> <a href="https://github.com/openyida/openyida" target="_blank" rel="noopener noreferrer" class="btn btn-outline-primary btn-sm w-100 rounded-pill"> View Original <i class="fas fa-external-link-alt ms-1"></i> </a> </div> </div> </div> <!-- Sidebar Related News --> <div class="card border-0 shadow-sm bg-surface"> <div class="card-header bg-transparent border-0 pt-4 pb-0 px-4"> <h5 class="card-title fw-bold mb-0" style="font-size: 1.05rem;"> <i class="fas fa-newspaper me-2 text-primary-custom"></i>Related Stories </h5> </div> <div class="card-body p-0"> <div class="list-group list-group-flush rounded-0 bg-transparent"> <a href="https://neshdevtech.com/news/giving-ai-pain-the-architecture-of-synthetic-scars-the-rapid-regret-miner-VRtlw" class="list-group-item list-group-item-action border-0 px-4 py-3 bg-transparent border-bottom border-light-custom hover-bg-light"> <h6 class="mb-1 fw-semibold text-truncate-2" style="font-size: 0.875rem; line-height: 1.4;"> Giving AI Pain: The Architecture of Synthetic Scars & The Rapid-Regret Miner </h6> <div class="d-flex align-items-center justify-content-between text-muted-custom mt-1" style="font-size: 0.72rem;"> <span>Dev.to</span> <span>23 hours ago</span> </div> </a> <a href="https://neshdevtech.com/news/yes-0-no-javascript-isnt-broken-jz8sP" class="list-group-item list-group-item-action border-0 px-4 py-3 bg-transparent border-bottom border-light-custom hover-bg-light"> <h6 class="mb-1 fw-semibold text-truncate-2" style="font-size: 0.875rem; line-height: 1.4;"> Yes, {} + [] = 0. No, JavaScript Isn’t Broken </h6> <div class="d-flex align-items-center justify-content-between text-muted-custom mt-1" style="font-size: 0.72rem;"> <span>Dev.to</span> <span>23 hours ago</span> </div> </a> <a href="https://neshdevtech.com/news/the-us-added-a-record-202-gwh-of-battery-storage-in-just-3-months-LJOFB" class="list-group-item list-group-item-action border-0 px-4 py-3 bg-transparent border-bottom border-light-custom hover-bg-light"> <h6 class="mb-1 fw-semibold text-truncate-2" style="font-size: 0.875rem; line-height: 1.4;"> The US added a record 20.2 GWh of battery storage in just 3 months </h6> <div class="d-flex align-items-center justify-content-between text-muted-custom mt-1" style="font-size: 0.72rem;"> <span>Hacker News</span> <span>1 day ago</span> </div> </a> <a href="https://neshdevtech.com/news/every-app-eventually-becomes-a-dashboard-please-make-it-stop-B8Nqj" class="list-group-item list-group-item-action border-0 px-4 py-3 bg-transparent border-bottom border-light-custom hover-bg-light"> <h6 class="mb-1 fw-semibold text-truncate-2" style="font-size: 0.875rem; line-height: 1.4;"> Every App Eventually Becomes a Dashboard. Please Make It Stop. </h6> <div class="d-flex align-items-center justify-content-between text-muted-custom mt-1" style="font-size: 0.72rem;"> <span>Dev.to</span> <span>1 day ago</span> </div> </a> <a href="https://neshdevtech.com/news/no-human-will-ever-write-code-better-than-llms-better-at-what-uKYsI" class="list-group-item list-group-item-action border-0 px-4 py-3 bg-transparent border-bottom border-light-custom hover-bg-light"> <h6 class="mb-1 fw-semibold text-truncate-2" style="font-size: 0.875rem; line-height: 1.4;"> No Human Will Ever Write Code Better Than LLMs? Better at What? </h6> <div class="d-flex align-items-center justify-content-between text-muted-custom mt-1" style="font-size: 0.72rem;"> <span>Dev.to</span> <span>1 day ago</span> </div> </a> </div> </div> </div> </div> </div> </div> <!-- Mobile: Related News horizontal scroll strip --> <div class="d-block d-lg-none mt-4"> <h6 class="fw-bold mb-3 px-1"> <i class="fas fa-newspaper me-2 text-primary-custom"></i>Related Stories </h6> <div class="related-scroll-strip"> <a href="https://neshdevtech.com/news/giving-ai-pain-the-architecture-of-synthetic-scars-the-rapid-regret-miner-VRtlw" class="related-scroll-card"> <p class="related-scroll-title">Giving AI Pain: The Architecture of Synthetic Scars & The Rapid-Regret...</p> <div class="related-scroll-meta"> <span>Dev.to</span> <span>23 hours ago</span> </div> </a> <a href="https://neshdevtech.com/news/yes-0-no-javascript-isnt-broken-jz8sP" class="related-scroll-card"> <p class="related-scroll-title">Yes, {} + [] = 0. No, JavaScript Isn’t Broken</p> <div class="related-scroll-meta"> <span>Dev.to</span> <span>23 hours ago</span> </div> </a> <a href="https://neshdevtech.com/news/the-us-added-a-record-202-gwh-of-battery-storage-in-just-3-months-LJOFB" class="related-scroll-card"> <p class="related-scroll-title">The US added a record 20.2 GWh of battery storage in just 3 months</p> <div class="related-scroll-meta"> <span>Hacker News</span> <span>1 day ago</span> </div> </a> <a href="https://neshdevtech.com/news/every-app-eventually-becomes-a-dashboard-please-make-it-stop-B8Nqj" class="related-scroll-card"> <p class="related-scroll-title">Every App Eventually Becomes a Dashboard. Please Make It Stop.</p> <div class="related-scroll-meta"> <span>Dev.to</span> <span>1 day ago</span> </div> </a> <a href="https://neshdevtech.com/news/no-human-will-ever-write-code-better-than-llms-better-at-what-uKYsI" class="related-scroll-card"> <p class="related-scroll-title">No Human Will Ever Write Code Better Than LLMs? Better at What?</p> <div class="related-scroll-meta"> <span>Dev.to</span> <span>1 day ago</span> </div> </a> </div> </div> </div> </section> </main> <!-- Footer --> <!-- Footer --> <footer class="py-5 mt-5 position-relative overflow-hidden" style="background: var(--surface); border-top: 1px solid var(--border); box-shadow: var(--shadow-lg);"> <!-- Decorative subtle background gradients --> <div class="position-absolute" style="top: -200px; right: -200px; width: 400px; height: 400px; background: rgba(var(--primary-rgb), 0.03); filter: blur(100px); border-radius: 50%; pointer-events: none;"></div> <div class="position-absolute" style="bottom: -200px; left: -200px; width: 400px; height: 400px; background: rgba(var(--accent-rgb), 0.03); filter: blur(100px); border-radius: 50%; pointer-events: none;"></div> <div class="container position-relative z-3"> <div class="row g-4"> <!-- Column 1: Brand & About --> <div class="col-lg-4 col-md-6"> <div class="d-flex align-items-center mb-3"> <span class="brand-mark me-2" style="width: 42px; height: 42px;" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> </span> <span class="fs-4 fw-extrabold text-primary-custom" style="letter-spacing: -0.05em; font-family: 'JetBrains Mono', monospace;">NeshDevTech</span> </div> <p class="text-secondary-custom mb-4 pe-lg-4" style="font-size: 0.95rem; line-height: 1.6;">NeshDevTech is a professional technology firm passionate about creating innovative, high-performance web and mobile solutions.</p> <div class="d-flex gap-2"> </div> </div> <!-- Column 2: Navigation Links --> <div class="col-lg-2 col-md-6 col-6"> <h6 class="text-primary-custom fw-bold text-uppercase mb-3" style="font-size: 0.85rem; letter-spacing: 0.05em;">Quick Navigation</h6> <ul class="list-unstyled mb-0"> <li class="mb-2"><a href="https://neshdevtech.com" class="footer-link text-secondary-custom text-decoration-none d-inline-block">Home</a></li> <li class="mb-2"><a href="https://neshdevtech.com/projects" class="footer-link text-secondary-custom text-decoration-none d-inline-block">Projects</a></li> <li class="mb-2"><a href="https://neshdevtech.com/blog" class="footer-link text-secondary-custom text-decoration-none d-inline-block">Tech Blog</a></li> <li class="mb-2"><a href="https://neshdevtech.com/pricing" class="footer-link text-secondary-custom text-decoration-none d-inline-block">Get Quote</a></li> <li class="mb-2"><a href="https://neshdevtech.com/contact" class="footer-link text-secondary-custom text-decoration-none d-inline-block">Contact Me</a></li> </ul> </div> <!-- Column 3: Tech Stack & Services --> <div class="col-lg-3 col-md-6 col-6"> <h6 class="text-primary-custom fw-bold text-uppercase mb-3" style="font-size: 0.85rem; letter-spacing: 0.05em;">Core Services</h6> <ul class="list-unstyled mb-0"> <li class="text-secondary-custom mb-2 small"><i class="bi bi-rocket-takeoff text-primary-custom me-2"></i>Custom Laravel Apps</li> <li class="text-secondary-custom mb-2 small"><i class="bi bi-laptop text-primary-custom me-2"></i>SPA (React & Vue.js)</li> <li class="text-secondary-custom mb-2 small"><i class="bi bi-phone-vibrate text-primary-custom me-2"></i>Flutter Mobile Apps</li> <li class="text-secondary-custom mb-2 small"><i class="bi bi-search text-primary-custom me-2"></i>SEO & Performance Opt</li> </ul> </div> <!-- Column 4: Contact & Availability --> <div class="col-lg-3 col-md-6"> <h6 class="text-primary-custom fw-bold text-uppercase mb-3" style="font-size: 0.85rem; letter-spacing: 0.05em;">Get in Touch</h6> <ul class="list-unstyled mb-3"> <li class="text-secondary-custom mb-2 small d-flex align-items-center"> <i class="bi bi-geo-alt text-primary-custom me-2 fs-6"></i>Nairobi, Kenya </li> <li class="text-secondary-custom mb-2 small d-flex align-items-center"> <i class="bi bi-envelope-at text-primary-custom me-2 fs-6"></i> <a href="mailto:info@neshdevtech.com" class="text-secondary-custom text-decoration-none footer-link">info@neshdevtech.com</a> </li> </ul> <div class="bg-primary-custom-light border border-primary-custom border-opacity-10 p-3 rounded-3" style="border-radius: 12px !important;"> <small class="d-block fw-semibold text-primary-custom mb-1"><i class="bi bi-patch-check-fill me-1"></i>Available for Projects</small> <p class="mb-0 text-secondary-custom small" style="font-size: 0.8rem; line-height: 1.4;">Need a high-performing system? Let's discuss your project.</p> </div> </div> </div> <hr style="border-color: var(--border); opacity: 0.8; margin: 2.5rem 0 2rem;"> <div class="row align-items-center"> <div class="col-md-6 text-center text-md-start mb-2 mb-md-0"> <p class="mb-0 text-secondary-custom small">© 2026 NeshDevTech. All rights reserved.</p> </div> <div class="col-md-6 text-center text-md-end"> <p class="mb-0 text-secondary-custom small">Built with <i class="bi bi-heart-fill text-danger"></i></p> </div> </div> </div> </footer> <!-- Bootstrap JS --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <!-- Custom JavaScript --> <script> // Theme Management System class ThemeManager { constructor() { this.themeToggle = document.getElementById('themeToggle'); this.themeIcon = document.getElementById('theme-icon'); this.mobileThemeToggle = document.getElementById('mobileThemeToggle'); this.mobileThemeIcon = document.getElementById('mobile-theme-icon'); this.init(); } init() { // Get current theme (already applied in head) const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; // Update icons to match current theme this.updateIcon(currentTheme); // Listen for desktop theme toggle clicks if (this.themeToggle) { this.themeToggle.addEventListener('click', (e) => { e.preventDefault(); this.toggleTheme(); }); } // Listen for mobile theme toggle clicks if (this.mobileThemeToggle) { this.mobileThemeToggle.addEventListener('click', (e) => { e.preventDefault(); this.toggleTheme(); }); } // Listen for system theme changes window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (!localStorage.getItem('theme')) { this.setTheme(e.matches ? 'dark' : 'light'); } }); } setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('data-bs-theme', theme); if (theme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } localStorage.setItem('theme', theme); this.updateThemeColor(theme); this.updateIcon(theme); } updateThemeColor(theme) { document.querySelectorAll('meta[name="theme-color"]').forEach((meta) => { meta.setAttribute('content', theme === 'dark' ? '#1c2028' : '#e4e8f0'); }); } toggleTheme() { const currentTheme = document.documentElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; this.setTheme(newTheme); // Close mobile menu if open const navbarCollapse = document.querySelector('.navbar-collapse'); if (navbarCollapse && navbarCollapse.classList.contains('show') && window.innerWidth < 992) { setTimeout(() => { const bsCollapse = new bootstrap.Collapse(navbarCollapse, { toggle: false }); bsCollapse.hide(); }, 150); } } updateIcon(theme) { const iconClass = theme === 'dark' ? 'bi bi-sun-fill' : 'bi bi-moon-fill'; // Update desktop icon if (this.themeIcon) { this.themeIcon.className = iconClass; } // Update mobile icon if (this.mobileThemeIcon) { this.mobileThemeIcon.className = iconClass; } } } // Initialize theme manager when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new ThemeManager(); // Initialize navbar live clock (function initNavClock(){ const clockEls = Array.from(document.querySelectorAll('[data-clock]')); if (!clockEls.length) return; const locale = navigator.language || 'en-US'; // Detect 12/24h by locale: use resolvedOptions hourCycle when available const dtf = new Intl.DateTimeFormat(locale, { hour: 'numeric' }); const uses12h = (dtf.resolvedOptions().hour12 === true); const fmt = new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: uses12h }); const update = () => { const now = new Date(); const text = fmt.format(now); clockEls.forEach(el => { el.textContent = text; }); }; update(); setInterval(update, 1000); })(); }); // Smooth scrolling for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // Navbar background on scroll window.addEventListener('scroll', () => { const navbar = document.querySelector('.navbar'); if (window.scrollY > 50) { navbar.style.backdropFilter = 'blur(20px)'; navbar.style.backgroundColor = 'rgba(var(--background-rgb), 0.8)'; } else { navbar.style.backdropFilter = 'blur(10px)'; navbar.style.backgroundColor = 'var(--background)'; } }); // Add fade-in animation to elements when they come into view const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-fade-in-up'); } }); }, observerOptions); // Observe elements with animation class document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.animate-on-scroll').forEach(el => { observer.observe(el); }); }); // Enhanced mobile menu functionality document.addEventListener('DOMContentLoaded', function() { const navbarToggler = document.querySelector('.navbar-toggler'); const navbarCollapse = document.querySelector('.navbar-collapse'); const navLinks = document.querySelectorAll('.navbar-nav .nav-link'); const socialLinks = document.querySelectorAll('.navbar-nav .d-flex a'); let isToggling = false; // Prevent rapid clicking // Enhanced menu close function function closeMenu() { if (navbarCollapse && navbarCollapse.classList.contains('show')) { // Use Bootstrap's collapse API for smooth transition const bsCollapse = new bootstrap.Collapse(navbarCollapse, { toggle: false }); bsCollapse.hide(); } // Reset hamburger animation and aria-expanded if (navbarToggler) { navbarToggler.classList.remove('active'); navbarToggler.setAttribute('aria-expanded', 'false'); } } // Enhanced hamburger toggle functionality if (navbarToggler) { navbarToggler.addEventListener('click', function(e) { e.preventDefault(); // Prevent rapid clicking if (isToggling) return; isToggling = true; const isMenuOpen = navbarCollapse.classList.contains('show'); if (isMenuOpen) { closeMenu(); } else { // Open menu const bsCollapse = new bootstrap.Collapse(navbarCollapse, { toggle: false }); bsCollapse.show(); navbarToggler.setAttribute('aria-expanded', 'true'); } // Reset toggle flag after animation setTimeout(() => { isToggling = false; }, 350); }); } // Close menu when clicking outside document.addEventListener('click', function(event) { const isClickInsideNav = navbarCollapse.contains(event.target); const isToggler = navbarToggler.contains(event.target); const isMenuOpen = navbarCollapse.classList.contains('show'); if (!isClickInsideNav && !isToggler && isMenuOpen && window.innerWidth < 992) { closeMenu(); } }); // Close menu when clicking on navigation links (mobile) navLinks.forEach(function(link) { link.addEventListener('click', function(e) { const isMenuOpen = navbarCollapse.classList.contains('show'); if (isMenuOpen && window.innerWidth < 992 && !this.classList.contains('dropdown-toggle')) { // Add a small delay to let the link navigation start setTimeout(() => { closeMenu(); }, 100); } }); }); // Close menu when clicking on social links (mobile) socialLinks.forEach(function(link) { link.addEventListener('click', function() { if (window.innerWidth < 992) { setTimeout(() => { closeMenu(); }, 100); } }); }); // Escape key closes menu document.addEventListener('keydown', function(event) { if (event.key === 'Escape' && window.innerWidth < 992) { closeMenu(); } }); // Handle window resize to close menu when switching to desktop window.addEventListener('resize', function() { if (window.innerWidth >= 992) { closeMenu(); } }); // Theme toggle functionality is handled by ThemeManager class // No separate event listener needed here // Prevent menu items from being too small on touch devices if ('ontouchstart' in window) { navLinks.forEach(function(link) { link.style.minHeight = '44px'; link.style.display = 'flex'; link.style.alignItems = 'center'; }); } // Add smooth scrolling for anchor links within pages navLinks.forEach(function(link) { const href = link.getAttribute('href'); if (href && href.startsWith('#')) { link.addEventListener('click', function(e) { e.preventDefault(); const target = document.querySelector(href); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } closeMenu(); }); } }); }); </script> <script> // Sticky navbar scroll enhancement (desktop only) (function () { const navbar = document.querySelector('.navbar'); if (!navbar) return; let ticking = false; const SCROLL_THRESHOLD = 20; // px function updateNavbar() { if (window.innerWidth >= 992) { if (window.scrollY > SCROLL_THRESHOLD) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); } } else { // Always remove on mobile so it doesn't affect mobile look navbar.classList.remove('scrolled'); } ticking = false; } window.addEventListener('scroll', function () { if (!ticking) { requestAnimationFrame(updateNavbar); ticking = true; } }, { passive: true }); window.addEventListener('resize', updateNavbar, { passive: true }); // Run once on page load in case page is already scrolled (e.g. back-navigation) updateNavbar(); })(); </script> <script> // Lightweight toast badge notifications (function(){ function ensureContainer(){ let c = document.getElementById('toast-badge-container'); if(!c){ c = document.createElement('div'); c.id = 'toast-badge-container'; c.setAttribute('aria-live','polite'); c.setAttribute('aria-atomic','true'); document.body.appendChild(c); } return c; } function iconFor(type){ switch(type){ case 'success': return '<i class="bi bi-check-circle"></i>'; case 'warning': return '<i class="bi bi-exclamation-triangle"></i>'; case 'error': return '<i class="bi bi-x-circle"></i>'; default: return '<i class="bi bi-info-circle"></i>'; } } window.notifyBadge = function(message, opts={}){ const { type = 'success', timeout = 2500 } = opts; const container = ensureContainer(); const el = document.createElement('div'); el.className = 'toast-badge ' + type; el.setAttribute('role','status'); el.innerHTML = '<span class="icon">'+iconFor(type)+'</span><span>'+message+'</span>'; container.appendChild(el); requestAnimationFrame(()=>{ el.classList.add('show'); }); const remove = () => { el.classList.remove('show'); setTimeout(()=> el.remove(), 200); }; setTimeout(remove, timeout); return { remove }; }; // Global copy helper used by inline buttons window.copyToClipboard = function(url){ try { navigator.clipboard.writeText(url).then(()=>{ // Update the clicked button, if available let btn = document.activeElement && document.activeElement.tagName === 'BUTTON' ? document.activeElement : (window.event && window.event.target && window.event.target.closest ? window.event.target.closest('button') : null); if (btn){ const original = btn.innerHTML; btn.innerHTML = '<i class="fas fa-check me-1"></i> Copied!'; btn.classList.remove('btn-outline-secondary'); btn.classList.add('btn-success'); setTimeout(()=>{ btn.innerHTML = original; btn.classList.remove('btn-success'); btn.classList.add('btn-outline-secondary'); }, 2000); } notifyBadge('Link copied to clipboard', { type: 'success' }); }).catch((err)=>{ console.error('Clipboard error:', err); notifyBadge('Could not copy link', { type: 'error' }); }); } catch (e) { console.error(e); notifyBadge('Copy not supported', { type: 'error' }); } }; })(); </script> <a href="https://wa.me/254768033943?text=Hi%2C+I+visited+your+website+and+would+like+to+inquire+about+your+services." class="whatsapp-btn-float" target="_blank" rel="noopener noreferrer" aria-label="Chat on WhatsApp"> <i class="fab fa-whatsapp"></i> <span class="whatsapp-tooltip">Chat with me</span> </a> <style> .whatsapp-btn-float { position: fixed; bottom: 24px; right: 24px; z-index: 1050; display: flex; align-items: center; justify-content: center; background-color: #25d366; color: #fff !important; width: 60px; height: 60px; border-radius: 50%; box-shadow: 0 4px 16px rgba(37, 211, 102, 0.45); text-decoration: none; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .whatsapp-btn-float i { font-size: 32px; transition: transform 0.3s ease; } .whatsapp-btn-float:hover { background-color: #128c7e; transform: scale(1.1); box-shadow: 0 6px 20px rgba(18, 140, 126, 0.6); } .whatsapp-btn-float:hover i { transform: rotate(10deg); } /* Pulsing ring animation */ .whatsapp-btn-float::before { content: ''; position: absolute; width: 100%; height: 100%; border-radius: 50%; background-color: #25d366; opacity: 0.7; z-index: -1; animation: whatsapp-pulse 2s infinite; } @keyframes whatsapp-pulse { 0% { transform: scale(1); opacity: 0.7; } 100% { transform: scale(1.6); opacity: 0; } } /* Tooltip style utilizing site-wide theme colors */ .whatsapp-btn-float .whatsapp-tooltip { position: absolute; right: 76px; background-color: var(--surface); color: var(--text-primary); border: 1px solid var(--border); padding: 8px 14px; border-radius: 20px; font-size: 14px; font-weight: 600; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateX(10px); transition: all 0.3s ease; box-shadow: var(--shadow); pointer-events: none; } .whatsapp-btn-float:hover .whatsapp-tooltip { opacity: 1; visibility: visible; transform: translateX(0); } /* Responsive Design */ @media (max-width: 768px) { .whatsapp-btn-float { bottom: 16px; right: 16px; width: 50px; height: 50px; } .whatsapp-btn-float i { font-size: 26px; } .whatsapp-btn-float .whatsapp-tooltip { display: none; /* Hide tooltip on small screens to prevent overlap issues */ } } </style> <!-- Mobile More Menu (Offcanvas Bottom Sheet) - Auth actions only --> <style> /* ══════════════════════════════════════════════════════════════ NEUMORPHISM REVAMP (front-end only) Soft-extruded UI: same-tone surfaces with dual light/dark shadows. This block sits at the end of the body so it layers over every page's own styles without touching page markup. ══════════════════════════════════════════════════════════════ */ /* ── Buttons: extruded, press-in on click ─────────────────────── */ .btn { box-shadow: var(--shadow) !important; transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important; } .btn:hover { transform: translateY(-2px) !important; box-shadow: var(--shadow-md) !important; } .btn:active:not(.btn-link), .btn.show { transform: translateY(1px) scale(0.99) !important; box-shadow: var(--shadow-inset) !important; } .btn-primary-custom, .btn-primary, .btn-success { background: var(--primary-color) !important; border-color: var(--primary-color) !important; color: #fff !important; box-shadow: var(--shadow) !important; } .btn-primary-custom:hover, .btn-primary:hover, .btn-success:hover { background: var(--primary-dark) !important; border-color: var(--primary-dark) !important; color: #fff !important; box-shadow: var(--shadow-md) !important; } .btn-outline-primary-custom, .btn-outline-primary, .btn-outline-success, .btn-outline-secondary, .btn-outline-danger, .btn-outline-dark, .btn-light { background-color: var(--surface) !important; border-color: transparent !important; color: var(--text-primary) !important; box-shadow: var(--shadow) !important; } .btn-outline-primary-custom:hover, .btn-outline-primary:hover { background-color: var(--primary-color) !important; border-color: var(--primary-color) !important; color: #fff !important; box-shadow: var(--shadow-md) !important; } .btn-outline-success:hover { background-color: var(--primary-color) !important; border-color: var(--primary-color) !important; color: #fff !important; box-shadow: var(--shadow-md) !important; } .btn-outline-secondary:hover { background-color: var(--text-secondary) !important; border-color: var(--text-secondary) !important; color: #fff !important; box-shadow: var(--shadow-md) !important; } .btn-outline-danger:hover { background-color: #ef4444 !important; border-color: #ef4444 !important; color: #fff !important; box-shadow: var(--shadow-md) !important; } .btn-outline-dark:hover, .btn-light:hover { background-color: var(--text-secondary) !important; border-color: var(--text-secondary) !important; color: var(--background) !important; box-shadow: var(--shadow-md) !important; } .btn-dark { background-color: var(--text-primary) !important; border-color: var(--text-primary) !important; color: var(--background) !important; box-shadow: var(--shadow) !important; } /* ── Forms: pressed-in surfaces ────────────────────────────────── */ .form-control, .form-select { background-color: var(--surface) !important; border: 1px solid transparent !important; color: var(--text-primary) !important; border-radius: 14px !important; box-shadow: var(--shadow-inset) !important; transition: box-shadow 0.3s ease, background-color 0.25s ease !important; } .form-control:focus, .form-select:focus { background-color: var(--surface) !important; border-color: transparent !important; color: var(--text-primary) !important; box-shadow: var(--shadow-inset), 0 0 0 4px rgba(var(--primary-rgb), 0.15) !important; } .form-control::placeholder { color: var(--text-muted) !important; } .input-group-text { background-color: var(--surface-alt) !important; border: 1px solid transparent !important; color: var(--text-secondary) !important; box-shadow: var(--shadow-inset-sm) !important; } /* ── Navbar: floating neumorphic pill (tablet & desktop) ──────── */ .navbar { background-color: rgba(var(--background-rgb), 0.9) !important; border-bottom: 1px solid transparent !important; backdrop-filter: blur(16px) !important; -webkit-backdrop-filter: blur(16px) !important; box-shadow: var(--shadow-md) !important; } [data-theme="dark"] .navbar { box-shadow: var(--shadow-md) !important; } @media (min-width: 768px) { .navbar { top: 14px !important; left: 18px !important; right: 18px !important; width: auto !important; border-radius: 20px !important; padding: 0.6rem 0 !important; box-shadow: var(--shadow-lg) !important; } [data-theme="dark"] .navbar { box-shadow: var(--shadow-lg) !important; } .navbar.scrolled { background-color: rgba(var(--background-rgb), 0.95) !important; border-bottom-color: transparent !important; padding: 0.5rem 0 !important; box-shadow: var(--shadow-lg) !important; } [data-theme="dark"] .navbar.scrolled { background-color: rgba(28, 32, 40, 0.95) !important; box-shadow: var(--shadow-lg) !important; } body { padding-top: 96px !important; } .reading-progress-container { top: 88px !important; } } /* ── Tablet refinements (768px - 991.98px) ────────────────────── */ @media (min-width: 768px) and (max-width: 991.98px) { /* Hamburger dropdown panel blends with the floating pill */ .navbar-collapse { border-radius: 20px !important; border-color: transparent !important; } body { padding-bottom: 0 !important; } .whatsapp-btn-float { bottom: 24px !important; } /* Hero: tighten the inline 80px top padding that assumed a full-height bar, so tablets don't get excessive empty space */ .hero-section-bg { padding-top: 40px !important; } } /* ── Neumorphic brand icon mark ────────────────────────────────── */ .brand-mark { width: 40px; height: 40px; flex-shrink: 0; border-radius: 13px; background-color: var(--surface) !important; color: var(--primary-color) !important; display: inline-flex; align-items: center; justify-content: center; box-shadow: var(--shadow) !important; transition: box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1), transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); } .brand-mark svg { width: 21px; height: 21px; } a:hover .brand-mark { box-shadow: var(--shadow-inset-sm) !important; transform: translateY(1px); } .mobile-logo .brand-mark { width: 34px; height: 34px; border-radius: 11px; } .mobile-logo .brand-mark svg { width: 17px; height: 17px; } /* ── Uploaded brand logo image ─────────────────────────────────── */ .brand-img { height: 38px; width: auto; max-width: 160px; object-fit: contain; flex-shrink: 0; } .brand-img-sm { height: 28px; max-width: 120px; } .brand-img-footer { height: 44px; max-width: 180px; } /* ── Mobile bars: floating neumorphic pills (phones) ──────────── */ @media (max-width: 767.98px) { .mobile-top-bar { top: 8px !important; left: 12px !important; right: 12px !important; height: 52px !important; border-radius: 16px !important; border-bottom: none !important; background-color: rgba(var(--surface-rgb), 0.92) !important; box-shadow: var(--shadow-md) !important; } .mobile-bottom-bar { left: 12px !important; right: 12px !important; bottom: 12px !important; height: 58px !important; border-radius: 18px !important; border-top: none !important; background-color: rgba(var(--surface-rgb), 0.92) !important; box-shadow: var(--shadow-md) !important; } body { padding-top: 74px !important; padding-bottom: 86px !important; } .whatsapp-btn-float { bottom: 88px !important; } .reading-progress-container { top: 70px !important; } } /* ── Nav pill widgets: pressed-in ──────────────────────────────── */ .theme-toggle, .mobile-theme-toggle, .nav-clock { background-color: var(--surface) !important; border-color: transparent !important; box-shadow: var(--shadow-inset-sm) !important; } /* ── Icon chips, markers & circles: extruded ───────────────────── */ .icon-wrap, .icon-wrapper, .expertise-card-icon, .timeline-marker, .mobile-more-icon-wrapper, .social-circle, .value-icon, .skill-icon-box { box-shadow: var(--shadow) !important; border-color: transparent !important; } /* ── Panels: extruded ──────────────────────────────────────────── */ .accordion-item { border-color: transparent !important; box-shadow: var(--shadow) !important; } .timeline-content { background: var(--surface) !important; border-radius: 12px !important; padding: 1rem 1.25rem !important; box-shadow: var(--shadow) !important; } .feature-item { background: var(--surface) !important; border-color: transparent !important; box-shadow: var(--shadow) !important; } .feature-item:hover { transform: translateY(-2px) !important; box-shadow: var(--shadow-md) !important; } .progress { background-color: var(--surface-alt) !important; box-shadow: var(--shadow-inset-sm) !important; } /* Card hover: consistent soft neu shadow */ .card-hover:hover, .project-card:hover, .blog-card:hover, .post-card:hover, .news-card:hover, .testimonial-card:hover, .hover-lift:hover, .stats-item:hover { box-shadow: var(--shadow-lg) !important; } </style> <script> document.addEventListener('DOMContentLoaded', function() { const readerCard = document.getElementById('reader-card'); if (!readerCard) return; // Settings Key constants const THEME_KEY = 'reader_mode_theme'; const FONT_KEY = 'reader_mode_font'; const SIZE_KEY = 'reader_mode_size'; // Available Options const themes = ['light', 'sepia', 'dark', 'oled']; const fonts = ['serif', 'sans']; const sizes = ['xs', 'sm', 'md', 'lg', 'xl']; // Default Configs const siteTheme = document.documentElement.getAttribute('data-theme') || 'light'; const defaultTheme = siteTheme === 'dark' ? 'dark' : 'light'; let currentTheme = localStorage.getItem(THEME_KEY) || defaultTheme; let currentFont = localStorage.getItem(FONT_KEY) || 'serif'; let currentSize = localStorage.getItem(SIZE_KEY) || 'md'; // Apply initial config applySettings(); // Register Action Listeners (Toolbar) document.getElementById('btn-font-serif').addEventListener('click', () => setFont('serif')); document.getElementById('btn-font-sans').addEventListener('click', () => setFont('sans')); document.getElementById('btn-size-decrease').addEventListener('click', () => adjustSize(-1)); document.getElementById('btn-size-increase').addEventListener('click', () => adjustSize(1)); document.getElementById('theme-light').addEventListener('click', () => setTheme('light')); document.getElementById('theme-sepia').addEventListener('click', () => setTheme('sepia')); document.getElementById('theme-dark').addEventListener('click', () => setTheme('dark')); document.getElementById('theme-oled').addEventListener('click', () => setTheme('oled')); // Register Action Listeners (Sidebar widgets, if present) const sidebarSerif = document.getElementById('sidebar-font-serif'); const sidebarSans = document.getElementById('sidebar-font-sans'); const sidebarSizeDec = document.getElementById('sidebar-size-dec'); const sidebarSizeInc = document.getElementById('sidebar-size-inc'); if (sidebarSerif) sidebarSerif.addEventListener('click', () => setFont('serif')); if (sidebarSans) sidebarSans.addEventListener('click', () => setFont('sans')); if (sidebarSizeDec) sidebarSizeDec.addEventListener('click', () => adjustSize(-1)); if (sidebarSizeInc) sidebarSizeInc.addEventListener('click', () => adjustSize(1)); // Action Core Functions function setTheme(theme) { currentTheme = theme; localStorage.setItem(THEME_KEY, theme); applySettings(); } function setFont(font) { currentFont = font; localStorage.setItem(FONT_KEY, font); applySettings(); } function adjustSize(delta) { let index = sizes.indexOf(currentSize); index = Math.max(0, Math.min(sizes.length - 1, index + delta)); currentSize = sizes[index]; localStorage.setItem(SIZE_KEY, currentSize); applySettings(); } function applySettings() { // 1. Reset theme classes and apply selected themes.forEach(t => readerCard.classList.remove('reader-theme-' + t)); readerCard.classList.add('reader-theme-' + currentTheme); // Update Theme circles highlight themes.forEach(t => { const circle = document.getElementById('theme-' + t); if (circle) { if (t === currentTheme) { circle.classList.add('active'); } else { circle.classList.remove('active'); } } }); // 2. Reset Font classes and apply selected fonts.forEach(f => readerCard.classList.remove('reader-font-' + f)); readerCard.classList.add('reader-font-' + currentFont); // Highlight Font buttons const serifBtns = [document.getElementById('btn-font-serif'), document.getElementById('sidebar-font-serif')]; const sansBtns = [document.getElementById('btn-font-sans'), document.getElementById('sidebar-font-sans')]; serifBtns.forEach(btn => { if (btn) { if (currentFont === 'serif') { btn.classList.add('btn-primary-custom'); btn.classList.remove('btn-light'); } else { btn.classList.add('btn-light'); btn.classList.remove('btn-primary-custom'); } } }); sansBtns.forEach(btn => { if (btn) { if (currentFont === 'sans') { btn.classList.add('btn-primary-custom'); btn.classList.remove('btn-light'); } else { btn.classList.add('btn-light'); btn.classList.remove('btn-primary-custom'); } } }); // 3. Reset Sizing classes and apply selected sizes.forEach(s => readerCard.classList.remove('reader-size-' + s)); readerCard.classList.add('reader-size-' + currentSize); } // Reading Scroll progress calculation const progressBar = document.getElementById('reading-progress-bar'); window.addEventListener('scroll', () => { const documentHeight = document.documentElement.scrollHeight - window.innerHeight; if (documentHeight > 0) { const scrollProgress = (window.pageYOffset / documentHeight) * 100; progressBar.style.width = scrollProgress + '%'; } }); }); </script> <script> // ══════════════════════════════════════════════════════════════ // PWA: Service Worker registration + install prompt // ══════════════════════════════════════════════════════════════ (function () { const PWA_VERSION = '20260805-1'; // ---- Service worker ------------------------------------ if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js?v=' + PWA_VERSION).then((reg) => { // Apply updates (new deploy) on the next load without disruption reg.addEventListener('updatefound', () => { const newWorker = reg.installing; if (!newWorker) return; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { newWorker.postMessage({ type: 'SKIP_WAITING' }); } }); }); }).catch(() => {}); }); } // ---- Install prompt -------------------------------------- let deferredPrompt = null; const bannerId = 'pwa-install-banner'; const DISMISS_KEY = 'nesh-install-dismissed'; function showInstallBanner() { if (localStorage.getItem(DISMISS_KEY) || document.getElementById(bannerId)) return; const banner = document.createElement('div'); banner.id = bannerId; banner.setAttribute('role', 'dialog'); banner.setAttribute('aria-label', 'Install app'); banner.innerHTML = '<div class="pwa-install-content">' + '<span class="pwa-install-icon">' + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>' + '</span>' + '<div class="pwa-install-text">' + '<strong>Install NeshDevTech</strong>' + '<span>Get the app on your device</span>' + '</div>' + '<button type="button" class="pwa-install-btn" id="pwa-install-btn">Install</button>' + '<button type="button" class="pwa-install-close" id="pwa-install-close" aria-label="Dismiss">×</button>' + '</div>'; document.body.appendChild(banner); document.getElementById('pwa-install-btn').addEventListener('click', () => { if (!deferredPrompt) return; deferredPrompt.prompt(); deferredPrompt.userChoice.finally(() => { deferredPrompt = null; hideInstallBanner(); }); }); document.getElementById('pwa-install-close').addEventListener('click', () => { localStorage.setItem(DISMISS_KEY, '1'); hideInstallBanner(); }); requestAnimationFrame(() => banner.classList.add('show')); } function hideInstallBanner() { const banner = document.getElementById(bannerId); if (banner) { banner.classList.remove('show'); setTimeout(() => banner.remove(), 250); } } window.addEventListener('beforeinstallprompt', (e) => { e.preventDefault(); deferredPrompt = e; showInstallBanner(); }); window.addEventListener('appinstalled', () => { deferredPrompt = null; hideInstallBanner(); localStorage.setItem(DISMISS_KEY, '1'); }); })(); </script> <style> .pwa-install-content { display: flex; align-items: center; gap: 12px; padding: 14px 18px; } #pwa-install-banner { position: fixed; left: 50%; transform: translate(-50%, 20px); bottom: 84px; z-index: 1070; background: var(--surface); color: var(--text-primary); border: 1px solid var(--border); border-radius: 18px; box-shadow: var(--shadow-lg); opacity: 0; transition: opacity .25s cubic-bezier(.16, 1, .3, 1), transform .25s cubic-bezier(.16, 1, .3, 1); width: min(420px, calc(100vw - 32px)); } #pwa-install-banner.show { opacity: 1; transform: translate(-50%, 0); } .pwa-install-icon { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; flex: 0 0 40px; border-radius: 12px; background: rgba(var(--primary-rgb), 0.1); border: 1px solid rgba(var(--primary-rgb), 0.2); color: var(--primary-color); } .pwa-install-icon svg { width: 20px; height: 20px; } .pwa-install-text { display: flex; flex-direction: column; flex: 1; min-width: 0; } .pwa-install-text strong { font-size: 0.92rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .pwa-install-text span { font-size: 0.78rem; color: var(--text-secondary); } .pwa-install-btn { background: var(--primary-color); border: none; color: #fff; font-weight: 600; font-size: 0.85rem; padding: 8px 18px; border-radius: 9999px; cursor: pointer; white-space: nowrap; box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.25); transition: all .2s ease; } .pwa-install-btn:hover { background: var(--primary-dark); transform: translateY(-1px); } .pwa-install-close { background: transparent; border: none; color: var(--text-muted); font-size: 1.4rem; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 50%; transition: background-color .2s ease, color .2s ease; } .pwa-install-close:hover { background: var(--surface-alt); color: var(--text-primary); } @media (min-width: 992px) { #pwa-install-banner { bottom: 24px; } } </style> </div><!-- /.page-wrapper --> </body> </html>