BLD - merge dev into Inter font branch
@@ -5,16 +5,18 @@ These JSON files are import-ready repository rulesets for `sky-systems/sky_phone
|
||||
## Activation order
|
||||
|
||||
1. Merge the governance files and workflows into the default `dev` branch.
|
||||
2. Let `CI` and `Pull request policy` run once so GitHub registers the check names.
|
||||
2. Let `CI`, `Automated code review`, and `Pull request policy` run once so GitHub registers the check names.
|
||||
3. Open **Settings > Rules > Rulesets > New ruleset > Import a ruleset**.
|
||||
4. Import `protect-dev.json`, review its target and required checks, then activate it.
|
||||
5. Import `protect-release-tags.json`, review the numeric tag pattern, then activate it.
|
||||
6. Open a test pull request and confirm all three required checks are reported before relying on the ruleset.
|
||||
6. Open a test pull request and confirm all five required checks and the test-resource comment are reported before relying on the ruleset.
|
||||
|
||||
`protect-dev.json` blocks deletion and force pushes, requires one approval, dismisses stale reviews, requires approval after the last push, resolves review conversations, and requires the `Repository policy`, `Frontend`, and `Pull request policy` checks.
|
||||
`protect-dev.json` blocks deletion and force pushes, restricts all updates of `dev` to the built-in `Maintain` role, requires one approval, dismisses stale reviews, requires approval after the last push, resolves review conversations, and requires the `Repository policy`, `Frontend`, `CodeQL`, `Dependency review`, and `Pull request policy` checks. Maintainers can bypass rules only through a pull request, so they cannot use this bypass for a direct push to `dev`.
|
||||
|
||||
Successful pull requests receive a 14-day test-resource artifact containing the deployable `sky_phone` folder and built NUI. `Pull request test resource link` runs after `CI` and maintains one download comment in the pull request. It does not check out, download, or execute pull-request content with its write-capable token.
|
||||
|
||||
`protect-release-tags.json` accepts stable numeric semantic versions such as `0.2.0`, rejects a leading `v`, and makes created tags immutable.
|
||||
|
||||
The built-in repository `Maintain` role (`RepositoryRole` actor ID `2`) has an explicit emergency bypass in both files. Use it only for audited recovery work. Remove the bypass in GitHub if maintainers should remain subject to every rule; changing the committed JSON alone does not update an already imported ruleset.
|
||||
The built-in repository `Maintain` role uses `RepositoryRole` actor ID `2`. Its branch bypass is limited to pull requests and is what permits maintainers to merge; its tag bypass is always available so maintainers can create and recover releases. Changing the committed JSON alone does not update an already imported ruleset.
|
||||
|
||||
Rulesets are GitHub settings, not live configuration files. Committing or editing these JSON files does not activate or update protection automatically; an administrator must import or reconcile them in GitHub.
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
{
|
||||
"type": "non_fast_forward"
|
||||
},
|
||||
{
|
||||
"type": "update",
|
||||
"parameters": {
|
||||
"update_allows_fetch_and_merge": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "pull_request",
|
||||
"parameters": {
|
||||
@@ -38,6 +44,12 @@
|
||||
{
|
||||
"context": "Frontend"
|
||||
},
|
||||
{
|
||||
"context": "CodeQL"
|
||||
},
|
||||
{
|
||||
"context": "Dependency review"
|
||||
},
|
||||
{
|
||||
"context": "Pull request policy"
|
||||
}
|
||||
@@ -50,7 +62,7 @@
|
||||
{
|
||||
"actor_id": 2,
|
||||
"actor_type": "RepositoryRole",
|
||||
"bypass_mode": "always"
|
||||
"bypass_mode": "pull_request"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ const rulesetFiles = (await readdir(rulesetDirectory)).filter((file) =>
|
||||
const requiredContexts = new Set([
|
||||
"Repository policy",
|
||||
"Frontend",
|
||||
"CodeQL",
|
||||
"Dependency review",
|
||||
"Pull request policy",
|
||||
]);
|
||||
|
||||
@@ -53,14 +55,23 @@ for (const file of rulesetFiles) {
|
||||
fail(`${file} must contain at least one rule`);
|
||||
}
|
||||
|
||||
const expectedBypassMode =
|
||||
ruleset.target === "branch" ? "pull_request" : "always";
|
||||
const maintainBypass = ruleset.bypass_actors?.some(
|
||||
(actor) =>
|
||||
actor.actor_type === "RepositoryRole" &&
|
||||
actor.actor_id === 2 &&
|
||||
actor.bypass_mode === "always",
|
||||
actor.bypass_mode === expectedBypassMode,
|
||||
);
|
||||
if (!maintainBypass) {
|
||||
fail(`${file} must retain the Maintain role emergency bypass`);
|
||||
fail(`${file} must retain the expected Maintain role bypass`);
|
||||
}
|
||||
|
||||
if (
|
||||
ruleset.target === "branch" &&
|
||||
!ruleset.rules?.some((rule) => rule.type === "update")
|
||||
) {
|
||||
fail(`${file} must restrict default-branch updates to bypass actors`);
|
||||
}
|
||||
|
||||
const statusRule = ruleset.rules?.find(
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Automated code review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: automated-review-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
|
||||
- name: Analyze JavaScript and TypeScript
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: /language:javascript-typescript
|
||||
|
||||
dependency-review:
|
||||
name: Dependency review
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Review dependency changes
|
||||
uses: actions/dependency-review-action@v5
|
||||
with:
|
||||
fail-on-severity: high
|
||||
@@ -92,3 +92,28 @@ jobs:
|
||||
|
||||
- name: Verify published NUI entrypoint
|
||||
run: test -f ../sky_phone/source/html/index.html
|
||||
|
||||
- name: Package pull request test resource
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
working-directory: .
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
artifact_name="sky_phone-pr-${PR_NUMBER}-${HEAD_SHA}"
|
||||
mkdir -p artifacts
|
||||
zip -r "artifacts/${artifact_name}.zip" sky_phone
|
||||
unzip -t "artifacts/${artifact_name}.zip"
|
||||
unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/fxmanifest.lua'
|
||||
unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/source/html/index.html'
|
||||
|
||||
- name: Upload pull request test resource
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: artifacts/sky_phone-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}.zip
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
name: Pull request test resource link
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- CI
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
name: Link test resource
|
||||
if: github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Create or update pull request comment
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const marker = '<!-- sky-phone-test-resource -->';
|
||||
const run = context.payload.workflow_run;
|
||||
const pullRequest = run.pull_requests?.[0];
|
||||
|
||||
if (!pullRequest) {
|
||||
core.notice('The CI run is not associated with a pull request.');
|
||||
return;
|
||||
}
|
||||
|
||||
const { owner, repo } = context.repo;
|
||||
const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}`;
|
||||
let body;
|
||||
|
||||
if (run.conclusion === 'success') {
|
||||
const response = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner,
|
||||
repo,
|
||||
run_id: run.id,
|
||||
per_page: 100,
|
||||
});
|
||||
const prefix = `sky_phone-pr-${pullRequest.number}-`;
|
||||
const artifact = response.data.artifacts.find(
|
||||
(candidate) =>
|
||||
!candidate.expired &&
|
||||
candidate.name.startsWith(prefix) &&
|
||||
/^sky_phone-pr-\d+-[0-9a-f]{40}\.zip$/.test(candidate.name),
|
||||
);
|
||||
|
||||
if (!artifact) {
|
||||
core.setFailed('CI succeeded without a pull request test resource artifact.');
|
||||
return;
|
||||
}
|
||||
|
||||
const artifactUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}/artifacts/${artifact.id}`;
|
||||
body = [
|
||||
marker,
|
||||
'## Sky Phone Test-Resource',
|
||||
'',
|
||||
`✅ Der aktuelle Pull Request wurde erfolgreich geprüft und gebaut. [Test-Resource herunterladen](${artifactUrl}).`,
|
||||
'',
|
||||
'Das ZIP enthält den deploybaren `sky_phone`-Ordner inklusive gebautem Frontend und bleibt 14 Tage verfügbar.',
|
||||
'',
|
||||
`[Workflow-Lauf öffnen](${runUrl})`,
|
||||
].join('\n');
|
||||
} else {
|
||||
body = [
|
||||
marker,
|
||||
'## Sky Phone Test-Resource',
|
||||
'',
|
||||
`❌ Für den aktuellen Stand wurde keine freigegebene Test-Resource erzeugt, weil der [CI-Lauf](${runUrl}) nicht erfolgreich war.`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pullRequest.number,
|
||||
per_page: 100,
|
||||
});
|
||||
const existing = comments.find(
|
||||
(comment) => comment.user?.type === 'Bot' && comment.body?.includes(marker),
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pullRequest.number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,44 @@ This repository contains the standalone FiveM resource `sky_phone`. These rules
|
||||
apply throughout the repository unless a more specific `AGENTS.md` adds stricter
|
||||
requirements.
|
||||
|
||||
## Repository governance for AI contributors
|
||||
|
||||
AI agents and automated coding tools must treat the repository governance files
|
||||
as project instructions, not as optional documentation.
|
||||
|
||||
- Before changing code, read `CONTRIBUTING.md` and every more specific
|
||||
`AGENTS.md` that applies to the target files. For issue or pull-request work,
|
||||
also read the matching file under `.github/ISSUE_TEMPLATE` and
|
||||
`.github/PULL_REQUEST_TEMPLATE.md`.
|
||||
- Before changing CI, packaging, releases, contribution policy, branch policy,
|
||||
or dependency handling, inspect all relevant files under `.github/workflows`,
|
||||
`.github/rulesets`, and `.github/scripts`. Keep their contracts synchronized.
|
||||
- Preserve the required check names `Repository policy`, `Frontend`, `CodeQL`,
|
||||
`Dependency review`, and `Pull request policy`. If a task intentionally
|
||||
renames or replaces one, update `.github/rulesets/protect-dev.json`,
|
||||
`.github/scripts/validate-repository.mjs`, `.github/rulesets/README.md`, and
|
||||
the corresponding workflow in the same change.
|
||||
- Anyone may open a pull request, but only collaborators with GitHub's built-in
|
||||
`Maintain` role may merge into `dev`, and maintainers must merge through a
|
||||
pull request. Preserve this behavior in branch rules and documentation.
|
||||
- Treat `.github/rulesets/*.json` as the version-controlled ruleset baseline.
|
||||
Editing or merging these files does not update GitHub settings automatically;
|
||||
report that an administrator must import or reconcile the live ruleset.
|
||||
- Preserve the secure PR artifact boundary: untrusted pull-request code may
|
||||
build the test resource only with read permissions. A workflow with write
|
||||
permissions may inspect trusted GitHub metadata and maintain the PR comment,
|
||||
but must never check out, download, extract, import, or execute PR-controlled
|
||||
code or artifacts.
|
||||
- PR and release packages must contain one top-level `sky_phone` directory with
|
||||
`fxmanifest.lua` and the built NUI at `source/html/index.html`. Build generated
|
||||
NUI from `frontend`; never hand-edit `sky_phone/source/html`.
|
||||
- Keep automated review and test-resource findings visible. Do not weaken,
|
||||
skip, or silence validation merely to make a check pass. Automated review
|
||||
complements and never replaces the required human maintainer review.
|
||||
- After governance changes, run the repository validator, Prettier, and
|
||||
actionlint where available. Inspect the final diff and stage only files that
|
||||
belong to the task.
|
||||
|
||||
## Architecture and security
|
||||
|
||||
- Keep `sky_phone` independent. Do not add dependencies or integrations with
|
||||
|
||||
@@ -75,10 +75,12 @@ pnpm build
|
||||
|
||||
The build publishes the generated NUI into `sky_phone/source/html`. Do not hand-edit generated output. A successful build proves source/build consistency, not behavior inside FiveM; report live runtime testing separately.
|
||||
|
||||
Every pull request also receives an automated CodeQL scan and dependency review. After the full CI run succeeds, GitHub packages the deployable `sky_phone` folder as a test-resource ZIP and adds or updates a download link in the pull request. The artifact is retained for 14 days. It is suitable for manual testing on a test server, but it is not a release and does not replace live FiveM validation.
|
||||
|
||||
Lua, config, manifest, locale, SQL, and native changes must also be tested in a restarted FiveM resource with experimental OAL enabled. Pass native coordinates as separate numeric arguments and verify native signatures against authoritative documentation.
|
||||
|
||||
## Review and merge
|
||||
|
||||
A pull request is ready when required checks pass, review conversations are resolved, the latest push is approved by someone other than its author, and migrations or operational steps are explicit. The default ruleset allows merge, squash, and rebase so maintainers can preserve meaningful merge history when needed.
|
||||
A pull request is ready when the repository policy, frontend, CodeQL, dependency review, and pull-request policy checks pass; review conversations are resolved; the latest push is approved by someone other than its author; and migrations or operational steps are explicit. Automated findings complement rather than replace the human maintainer review. Anyone may open a pull request, but only collaborators with the built-in GitHub `Maintain` role may merge into `dev`. Maintainers must merge through a pull request; the ruleset does not permit direct pushes to `dev`. The default ruleset allows merge, squash, and rebase so maintainers can preserve meaningful merge history when needed.
|
||||
|
||||
Release tags use numeric semantic versions without a `v` prefix, for example `0.2.0`. Tags are immutable after creation.
|
||||
|
||||
@@ -114,25 +114,27 @@ Sky Phone is built to be the **free FiveM phone you can choose without accepting
|
||||
|
||||
### Required
|
||||
|
||||
- MySQL or MariaDB
|
||||
- `oxmysql`
|
||||
- One supported framework:
|
||||
- ESX Legacy (`es_extended`)
|
||||
- Qbox (`qbx_core`)
|
||||
- QBCore (`qb-core`)
|
||||
- One supported inventory path:
|
||||
- `ox_inventory`
|
||||
- `qb-inventory`
|
||||
- `lj-inventory`
|
||||
- `qs-inventory`
|
||||
- `codem-inventory`
|
||||
- `core_inventory`
|
||||
- `mf-inventory`
|
||||
- `smx-inventory`
|
||||
- `hex_4_inventory`
|
||||
- Native ESX inventory
|
||||
| Requirement | Supported options |
|
||||
| --- | --- |
|
||||
| **Database** | MySQL or MariaDB |
|
||||
| **Database bridge** | `oxmysql` |
|
||||
| **Framework** | ESX Legacy (`es_extended`), QBCore (`qb-core`), or Qbox (`qbx_core`) |
|
||||
| **Inventory** | Choose one supported adapter from the table below |
|
||||
|
||||
`mf-inventory` and `smx-inventory` are supported with ESX. The native ESX and HEX adapters use count-based items and therefore require unique phones and physical SIM cards to be disabled.
|
||||
| Inventory | Metadata support | Unique Phones | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `ox_inventory` | Yes | Yes | Full per-item phone and physical SIM metadata |
|
||||
| `qb-inventory` | Yes | Yes | Uses item `info` metadata |
|
||||
| `lj-inventory` | Yes | Yes | QBCore inventory with item `info` metadata |
|
||||
| `qs-inventory` | Yes | Yes | Full per-slot metadata |
|
||||
| `codem-inventory` | Yes | Yes | Full per-slot metadata |
|
||||
| `core_inventory` | Yes | Yes | Full per-slot metadata |
|
||||
| `mf-inventory` | Yes | Yes | Supported with ESX |
|
||||
| `smx-inventory` | Yes | Yes | Supported with ESX through the player metadata bridge |
|
||||
| `hex_4_inventory` | **No metadata support** | **No, Unique Phones are not possible** | ESX only; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` |
|
||||
| Native ESX inventory | **No metadata support** | **No, Unique Phones are not possible** | Count-based items; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` |
|
||||
|
||||
`hex_4_inventory` and native ESX inventory cannot persist per-item metadata. Unique Phones and physical SIM cards are therefore unavailable with these adapters.
|
||||
|
||||
### Voice
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
"test": "vitest run && pnpm test:browser-mocks",
|
||||
"test:browser-mocks": "node testserver/smoke.cjs"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"nanoid@<3.3.18": "3.3.18"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^3.29.2",
|
||||
"@tiptap/extension-placeholder": "^3.29.2",
|
||||
|
||||
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
nanoid@<3.3.18: 3.3.18
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -1940,8 +1943,8 @@ packages:
|
||||
muggle-string@0.4.1:
|
||||
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
|
||||
|
||||
nanoid@3.3.17:
|
||||
resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==}
|
||||
nanoid@3.3.18:
|
||||
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
@@ -4351,7 +4354,7 @@ snapshots:
|
||||
|
||||
muggle-string@0.4.1: {}
|
||||
|
||||
nanoid@3.3.17: {}
|
||||
nanoid@3.3.18: {}
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
@@ -4449,7 +4452,7 @@ snapshots:
|
||||
|
||||
postcss@8.5.25:
|
||||
dependencies:
|
||||
nanoid: 3.3.17
|
||||
nanoid: 3.3.18
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
@@ -268,8 +268,13 @@ const PHONE_BASE_SCALE = 0.69 * 1.2
|
||||
const PHONE_PORTRAIT_WIDTH = 390
|
||||
const PHONE_PORTRAIT_HEIGHT = 844
|
||||
const MIN_PRODUCTION_PHONE_ZOOM = 260 / PHONE_PORTRAIT_WIDTH
|
||||
const isDevelopment = import.meta.env.DEV
|
||||
const developmentParameters = new URLSearchParams(window.location.search)
|
||||
const isBrowserPreview =
|
||||
developmentParameters.has('browserPreview') &&
|
||||
developmentParameters.get('apiBase')?.startsWith('/') === true
|
||||
const isDevelopment =
|
||||
import.meta.env.DEV ||
|
||||
developmentParameters.get('apiBase')?.startsWith('/') === true
|
||||
const developmentLockScreenPreview =
|
||||
isDevelopment && developmentParameters.has('lockScreenPreview')
|
||||
|
||||
@@ -433,6 +438,14 @@ let simPickerClosePending = false
|
||||
|
||||
function getViewportScale(): number {
|
||||
const heightScale = window.innerHeight / REFERENCE_VIEWPORT_HEIGHT
|
||||
if (isBrowserPreview) {
|
||||
const availableScale = Math.min(
|
||||
window.innerWidth / PHONE_PORTRAIT_WIDTH,
|
||||
window.innerHeight / PHONE_PORTRAIT_HEIGHT,
|
||||
)
|
||||
|
||||
return (availableScale * 0.94) / PHONE_BASE_SCALE
|
||||
}
|
||||
if (isDevelopment) return heightScale
|
||||
|
||||
return Math.min(window.innerWidth / REFERENCE_VIEWPORT_WIDTH, heightScale)
|
||||
@@ -1583,6 +1596,7 @@ onBeforeUnmount(() => {
|
||||
"
|
||||
class="phone-stage"
|
||||
:class="{
|
||||
'phone-stage--browser-preview': isBrowserPreview,
|
||||
'phone-stage--landscape': phone.cameraLandscape,
|
||||
'phone-stage--peek': notifications.isPeeking,
|
||||
}"
|
||||
|
||||
@@ -84,4 +84,16 @@ describe('browser development preview contract', () => {
|
||||
)
|
||||
expect(mainCss).not.toContain('.phone-stage--dev')
|
||||
})
|
||||
|
||||
it('fills the dedicated browser embed without clipping the hardware controls', () => {
|
||||
expect(source).toContain("developmentParameters.has('browserPreview')")
|
||||
expect(source).toContain("'phone-stage--browser-preview': isBrowserPreview")
|
||||
expect(source).toContain('return (availableScale * 0.94) / PHONE_BASE_SCALE')
|
||||
expect(mainCss).toMatch(
|
||||
/\.phone-stage--browser-preview\s*\{[^}]*place-items:\s*center;[^}]*padding:\s*0;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.phone-stage--browser-preview \.phone-device\s*\{[^}]*filter:\s*none;/s,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="Billing">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="18" y1="14" x2="108" y2="116" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ff856f"/>
|
||||
<stop offset="1" stop-color="#ff4f64"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="29" fill="url(#bg)"/>
|
||||
<path d="M38 26h52v76l-7-5-7 5-7-5-7 5-7-5-7 5-10-7V26Z" fill="#fff"/>
|
||||
<path d="M49 45h30M49 58h30M49 71h18" fill="none" stroke="#ff6370" stroke-linecap="round" stroke-width="7"/>
|
||||
<circle cx="80" cy="75" r="8" fill="#ff6370"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 610 B |
|
After Width: | Height: | Size: 4.6 KiB |
@@ -1,26 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="Calendar">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="32" y1="20" x2="224" y2="236" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ff765f"/>
|
||||
<stop offset="1" stop-color="#e93147"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="150%">
|
||||
<feDropShadow dx="0" dy="10" stdDeviation="10" flood-color="#8c1021" flood-opacity=".32"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="256" height="256" rx="58" fill="url(#bg)"/>
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="42" y="50" width="172" height="164" rx="28" fill="#fff"/>
|
||||
<path d="M42 78c0-15.5 12.5-28 28-28h116c15.5 0 28 12.5 28 28v26H42V78Z" fill="#f8e9eb"/>
|
||||
<rect x="77" y="36" width="14" height="42" rx="7" fill="#fff"/>
|
||||
<rect x="165" y="36" width="14" height="42" rx="7" fill="#fff"/>
|
||||
<g fill="#d5d8df">
|
||||
<rect x="67" y="124" width="26" height="21" rx="7"/><rect x="104" y="124" width="26" height="21" rx="7"/><rect x="141" y="124" width="26" height="21" rx="7"/>
|
||||
<rect x="67" y="157" width="26" height="21" rx="7"/><rect x="141" y="157" width="26" height="21" rx="7"/>
|
||||
</g>
|
||||
<rect x="101" y="153" width="32" height="29" rx="9" fill="#ff4259"/>
|
||||
<path d="m109 167 7 7 11-14" fill="none" stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<circle cx="201" cy="198" r="27" fill="#272b36" stroke="#fff" stroke-width="6"/>
|
||||
<path d="M201 183v16l10 7" fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 44 KiB |
@@ -1,29 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" role="img">
|
||||
<defs>
|
||||
<linearGradient id="companies-bg" x1="18" y1="12" x2="104" y2="110" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#38BDF8"/>
|
||||
<stop offset="1" stop-color="#2563EB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="companies-glass" x1="42" y1="31" x2="86" y2="101" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFFFFF" stop-opacity=".96"/>
|
||||
<stop offset="1" stop-color="#DBEAFE" stop-opacity=".82"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="120" height="120" rx="27" fill="url(#companies-bg)"/>
|
||||
<path d="M26 96V45c0-5 4-9 9-9h26c5 0 9 4 9 9v51H26Z" fill="url(#companies-glass)"/>
|
||||
<path d="M60 96V28c0-5 4-9 9-9h18c5 0 9 4 9 9v68H60Z" fill="#EFF6FF" fill-opacity=".9"/>
|
||||
<g fill="#3B82F6" fill-opacity=".82">
|
||||
<rect x="36" y="48" width="9" height="9" rx="2"/>
|
||||
<rect x="51" y="48" width="9" height="9" rx="2"/>
|
||||
<rect x="36" y="64" width="9" height="9" rx="2"/>
|
||||
<rect x="51" y="64" width="9" height="9" rx="2"/>
|
||||
<rect x="70" y="33" width="8" height="8" rx="2"/>
|
||||
<rect x="83" y="33" width="8" height="8" rx="2"/>
|
||||
<rect x="70" y="48" width="8" height="8" rx="2"/>
|
||||
<rect x="83" y="48" width="8" height="8" rx="2"/>
|
||||
<rect x="70" y="63" width="8" height="8" rx="2"/>
|
||||
<rect x="83" y="63" width="8" height="8" rx="2"/>
|
||||
</g>
|
||||
<path d="M18 96h84" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="96" cy="25" r="10" fill="#22C55E" stroke="#FFFFFF" stroke-width="4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -1,21 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="CrewLink">
|
||||
<defs>
|
||||
<linearGradient id="crewlink-bg" x1="16" y1="12" x2="112" y2="118" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#24e2f2"/>
|
||||
<stop offset="0.52" stop-color="#0a9bf5"/>
|
||||
<stop offset="1" stop-color="#5746e8"/>
|
||||
</linearGradient>
|
||||
<filter id="crewlink-glow" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feGaussianBlur stdDeviation="3.5" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="29" fill="url(#crewlink-bg)"/>
|
||||
<path d="M20 78c8-29 24-44 44-44s36 15 44 44" fill="none" stroke="#fff" stroke-opacity=".25" stroke-width="7" stroke-linecap="round"/>
|
||||
<path d="M31 84c7-21 18-31 33-31s26 10 33 31" fill="none" stroke="#fff" stroke-opacity=".45" stroke-width="7" stroke-linecap="round"/>
|
||||
<circle cx="64" cy="78" r="13" fill="#fff" filter="url(#crewlink-glow)"/>
|
||||
<circle cx="34" cy="86" r="10" fill="#d8fbff"/>
|
||||
<circle cx="94" cy="86" r="10" fill="#d8fbff"/>
|
||||
<path d="M64 102c-11-11-18-20-18-31a18 18 0 1 1 36 0c0 11-7 20-18 31Z" fill="#fff" fill-opacity=".18" stroke="#fff" stroke-width="3"/>
|
||||
<circle cx="64" cy="71" r="5" fill="#fff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -1,15 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="Feather">
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="18" y1="14" x2="108" y2="116" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#71C8FF"/>
|
||||
<stop offset="0.48" stop-color="#438CF5"/>
|
||||
<stop offset="1" stop-color="#2757D8"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="5" stdDeviation="5" flood-color="#0B2F82" flood-opacity=".25"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="30" fill="url(#sky)"/>
|
||||
<path d="M96.7 28.3C79.9 28 59.4 38.6 48.2 55.8c-8.7 13.3-9.8 27.7-6 36.7l-10 10.2a5 5 0 0 0 7.1 7l10.1-10.3c9 3.2 21.3 1.3 31.7-6.4 14.8-11 22.9-31.9 20.8-59.9-.2-2.7-2.5-4.8-5.2-4.8Z" fill="#fff" filter="url(#shadow)"/>
|
||||
<path d="M45.5 97.7c12.7-15.3 25.9-28.6 42.4-42.3M54.5 85.8l21.8-.7M66 72.6l1.4-20" fill="none" stroke="#347BE7" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1012 B |
|
After Width: | Height: | Size: 30 KiB |
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="18" y1="112" x2="108" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ff2d78"/>
|
||||
<stop offset="1" stop-color="#ff6847"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="29" fill="url(#g)"/>
|
||||
<path fill="#fff" d="M67 18c3 19-13 25-13 41 0 8 5 13 10 16-1-13 8-19 17-29 1 15 19 22 19 41 0 17-15 29-36 29S28 103 28 84c0-20 13-31 26-43 6-6 10-13 13-23Z"/>
|
||||
<path fill="#ff4e63" d="M66 105c-11 0-19-7-19-17 0-9 7-14 13-20 0 8 7 10 7 17 4-4 7-8 8-13 6 6 10 11 10 18 0 9-8 15-19 15Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 633 B |
|
After Width: | Height: | Size: 7.2 KiB |
@@ -1,17 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="House">
|
||||
<defs>
|
||||
<linearGradient id="house-bg" x1="18" y1="12" x2="108" y2="118" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ffb84d"/>
|
||||
<stop offset="1" stop-color="#f06b35"/>
|
||||
</linearGradient>
|
||||
<filter id="house-shadow" x="-20%" y="-20%" width="140%" height="150%">
|
||||
<feDropShadow dx="0" dy="5" stdDeviation="5" flood-color="#8f351c" flood-opacity=".28"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="29" fill="url(#house-bg)"/>
|
||||
<circle cx="101" cy="25" r="23" fill="#fff" opacity=".13"/>
|
||||
<path d="M27 61.5 64 31l37 30.5v36a9 9 0 0 1-9 9H36a9 9 0 0 1-9-9z" fill="#fff" filter="url(#house-shadow)"/>
|
||||
<path d="M19 64.5 64 27l45 37.5" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="11"/>
|
||||
<rect x="53" y="73" width="22" height="33.5" rx="5" fill="#f4823c"/>
|
||||
<circle cx="69" cy="90" r="2.4" fill="#fff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 993 B |
|
After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="Music">
|
||||
<defs>
|
||||
<linearGradient id="music-gradient" x1="16" y1="8" x2="112" y2="120" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ff5c78"/>
|
||||
<stop offset=".52" stop-color="#fa2d63"/>
|
||||
<stop offset="1" stop-color="#d5117b"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="28" fill="url(#music-gradient)"/>
|
||||
<path fill="#fff" d="M91.5 24.5v57.3c0 11.8-8.8 20.7-20.4 20.7-9.3 0-16.7-5.7-16.7-13.6 0-8.2 7.8-14.5 17.9-14.5 4.1 0 7.4.9 9.8 2.2V45.4L50 52.1v38.1c0 11.8-8.8 20.7-20.4 20.7-9.3 0-16.7-5.7-16.7-13.6 0-8.2 7.8-14.5 17.9-14.5 4 0 7.4.9 9.8 2.2V36.8c0-3.2 2.2-5.9 5.3-6.5l38.2-8c3.8-.8 7.4 2.1 7.4 6.2Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 754 B |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@@ -1,52 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="256" y1="0" x2="256" y2="512" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#55c9ec"/>
|
||||
<stop offset="0.58" stop-color="#7f91e4"/>
|
||||
<stop offset="1" stop-color="#5b4da6"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="tower" x1="330" y1="0" x2="488" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8276e4"/>
|
||||
<stop offset="0.48" stop-color="#6559ca"/>
|
||||
<stop offset="1" stop-color="#433a9c"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="bird" x1="20" y1="24" x2="90" y2="52" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5fc1d1"/>
|
||||
<stop offset="0.58" stop-color="#287d99"/>
|
||||
<stop offset="1" stop-color="#16546f"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="512" height="512" fill="url(#sky)"/>
|
||||
<circle cx="112" cy="105" r="46" fill="#ffe6a5" opacity=".78"/>
|
||||
<path d="M-18 155c20-25 55-28 78-8 15-29 61-27 75 5 26-9 55 7 59 32H-18Z" fill="#fff" opacity=".3"/>
|
||||
<path d="M2 405c25-29 66-30 88-4 19-16 50-9 58 17H2Z" fill="#fff" opacity=".2"/>
|
||||
|
||||
<g stroke="#312b78" stroke-width="9" stroke-linejoin="round">
|
||||
<path d="M357-12h112v171H357Z" fill="url(#tower)"/>
|
||||
<path d="M329 148h168v52H329Z" fill="url(#tower)"/>
|
||||
<path d="M329 323h168v52H329Z" fill="url(#tower)"/>
|
||||
<path d="M357 364h112v160H357Z" fill="url(#tower)"/>
|
||||
</g>
|
||||
<path d="M373 0v145M345 164h132M345 339h132M373 379v133" fill="none" stroke="#aaa2ff" stroke-width="10" stroke-linecap="round" opacity=".62"/>
|
||||
|
||||
<g fill="none" stroke="#e9fbff" stroke-linecap="round" opacity=".7">
|
||||
<path d="M24 257h63" stroke-width="12"/>
|
||||
<path d="M39 288h38" stroke-width="8"/>
|
||||
<path d="M52 224h52" stroke-width="7"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(70 202) scale(2.15)" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M50 38C39 29 30 15 34 5c10 4 22 16 29 31Z" fill="#2d7188" stroke="#123f57" stroke-width="2"/>
|
||||
<path d="M29 36 5 25l12 15L3 49l29-4Z" fill="#285e78" stroke="#123f57" stroke-width="2"/>
|
||||
<path d="M20 40c8-12 24-17 45-14 12 1 20 7 25 14-8 10-21 13-36 12-15-1-27-4-34-12Z" fill="url(#bird)" stroke="#123f57" stroke-width="2"/>
|
||||
<path d="M24 43c13 4 31 5 45 1 7-2 12-5 16-9 2 2 4 4 5 6-8 10-21 13-36 12-13-1-24-4-30-10Z" fill="#b7e5df"/>
|
||||
<path d="M67 27c7-8 18-9 24-2 4 4 4 10 1 16-8 1-16-3-23-9Z" fill="#377f98"/>
|
||||
<path d="M52 38C42 31 35 18 40 6c10 5 20 16 27 29-1 8-7 17-19 25 2-8 3-15 4-22Z" fill="#77b6c2" stroke="#123f57" stroke-width="2"/>
|
||||
<path d="M44 16c8 7 14 15 18 23M46 27c7 5 11 11 13 18M48 39c4 3 7 6 8 10" fill="none" stroke="#d4f0ed" stroke-width="1.5"/>
|
||||
<path d="M76 26c5-5 12-4 16 1-4 1-7 4-9 8-4-1-7-4-7-9Z" fill="#d8f0e9"/>
|
||||
<circle cx="86" cy="27" r="3.1" fill="#f5fbf7"/>
|
||||
<circle cx="86.7" cy="27" r="1.45" fill="#102d3d"/>
|
||||
<path d="m91 31 12 5-12 4c2-3 2-6 0-9Z" fill="#f2a84b" stroke="#6f4727" stroke-width="1.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,34 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="skyride-bg" x1="62" y1="26" x2="446" y2="488" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFE66A"/>
|
||||
<stop offset="0.48" stop-color="#FFC62E"/>
|
||||
<stop offset="1" stop-color="#FF9500"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="skyride-car" x1="154" y1="192" x2="352" y2="388" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFFFFF"/>
|
||||
<stop offset="1" stop-color="#ECEEF2"/>
|
||||
</linearGradient>
|
||||
<filter id="skyride-shadow" x="88" y="102" width="346" height="340" filterUnits="userSpaceOnUse">
|
||||
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#402400" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect width="512" height="512" rx="112" fill="url(#skyride-bg)"/>
|
||||
<path d="M63 128C146 88 218 112 251 169C285 226 362 224 452 163" fill="none" stroke="#171717" stroke-opacity="0.15" stroke-width="20" stroke-linecap="round" stroke-dasharray="2 40"/>
|
||||
<path d="M75 405C145 347 191 354 242 389C292 424 357 435 441 389" fill="none" stroke="#171717" stroke-opacity="0.12" stroke-width="18" stroke-linecap="round" stroke-dasharray="2 38"/>
|
||||
|
||||
<g filter="url(#skyride-shadow)">
|
||||
<path d="M370 104C329 104 296 137 296 178C296 236 370 293 370 293C370 293 444 236 444 178C444 137 411 104 370 104Z" fill="#171717"/>
|
||||
<circle cx="370" cy="178" r="28" fill="#FFD137"/>
|
||||
|
||||
<path d="M183 199C190 181 207 169 226 169H286C305 169 322 181 329 199L350 251C371 260 385 281 385 305V371C385 384 375 394 362 394H342C329 394 319 384 319 371V360H193V371C193 384 183 394 170 394H150C137 394 127 384 127 371V305C127 281 141 260 162 251L183 199Z" fill="url(#skyride-car)"/>
|
||||
<path d="M207 205C211 196 220 190 230 190H282C292 190 301 196 305 205L320 244H192L207 205Z" fill="#20232A"/>
|
||||
<path d="M238 151H274C282 151 289 157 289 166V179H223V166C223 157 230 151 238 151Z" fill="#171717"/>
|
||||
<rect x="235" y="158" width="42" height="13" rx="6.5" fill="#FFD137"/>
|
||||
<path d="M155 279H357C364 279 370 285 370 292V335C370 342 364 348 357 348H155C148 348 142 342 142 335V292C142 285 148 279 155 279Z" fill="#FFFFFF"/>
|
||||
<path d="M158 291L205 300L188 325H156C150 325 146 321 146 315V301C146 294 151 290 158 291Z" fill="#171717"/>
|
||||
<path d="M354 291L307 300L324 325H356C362 325 366 321 366 315V301C366 294 361 290 354 291Z" fill="#171717"/>
|
||||
<rect x="224" y="312" width="64" height="12" rx="6" fill="#171717"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="Weazel News">
|
||||
<defs>
|
||||
<linearGradient id="weazel-news-background" x1="18" y1="10" x2="112" y2="122" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#171717"/>
|
||||
<stop offset="1" stop-color="#050505"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="weazel-news-accent" x1="26" y1="24" x2="103" y2="105" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ff4a50"/>
|
||||
<stop offset="1" stop-color="#c9101c"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="28" fill="url(#weazel-news-background)"/>
|
||||
<path d="M28 31h72a8 8 0 0 1 8 8v58a8 8 0 0 1-8 8H28a8 8 0 0 1-8-8V39a8 8 0 0 1 8-8Z" fill="#f4f4f1"/>
|
||||
<path d="M20 46h88v-7a8 8 0 0 0-8-8H28a8 8 0 0 0-8 8v7Z" fill="url(#weazel-news-accent)"/>
|
||||
<path d="M31 58h34v26H31z" fill="#181818"/>
|
||||
<path d="M72 59h25M72 68h25M72 77h20M31 92h66" fill="none" stroke="#747474" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="m38 63 6 16 5-12 5 12 6-16" fill="none" stroke="#f4f4f1" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="96" cy="38" r="3" fill="#fff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
@@ -252,6 +252,13 @@ button {
|
||||
padding: 0 var(--phone-edge-gap, 24px) var(--phone-edge-gap, 24px) 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.phone-stage--browser-preview {
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
.phone-stage--browser-preview .phone-device {
|
||||
filter: none;
|
||||
}
|
||||
.phone-stage--peek .phone-resolution-wrapper--primary .phone-device {
|
||||
transform: translateY(calc(100% - 190px));
|
||||
transform-origin: right bottom;
|
||||
|
||||
@@ -45,7 +45,7 @@ import appStoreIcon from '@/assets/img/app-icons/apps.webp'
|
||||
import calculatorIcon from '@/assets/img/app-icons/calculator.webp'
|
||||
import cameraIcon from '@/assets/img/app-icons/camera.webp'
|
||||
import clockIcon from '@/assets/img/app-icons/clock.webp'
|
||||
import calendarIcon from '@/assets/img/app-icons/calendar.svg'
|
||||
import calendarIcon from '@/assets/img/app-icons/calendar.webp'
|
||||
import mailIcon from '@/assets/img/app-icons/mail.webp'
|
||||
import mapIcon from '@/assets/img/app-icons/map.webp'
|
||||
import messagesIcon from '@/assets/img/app-icons/sms.webp'
|
||||
@@ -67,20 +67,20 @@ import weatherIcon from '@/assets/img/app-icons/weather.webp'
|
||||
import healthIcon from '@/assets/img/app-icons/health.webp'
|
||||
import bankingIcon from '@/assets/img/app-icons/banking.webp'
|
||||
import cryptoIcon from '@/assets/img/app-icons/crypto.webp'
|
||||
import billingIcon from '@/assets/img/app-icons/billing.svg'
|
||||
import billingIcon from '@/assets/img/app-icons/billing.webp'
|
||||
import garageIcon from '@/assets/img/app-icons/garage.webp'
|
||||
import houseIcon from '@/assets/img/app-icons/house.svg'
|
||||
import houseIcon from '@/assets/img/app-icons/house.webp'
|
||||
import citymarktIcon from '@/assets/img/app-icons/citymarkt.webp'
|
||||
import localPagesIcon from '@/assets/img/app-icons/local-pages.webp'
|
||||
import flareIcon from '@/assets/img/app-icons/flare.svg'
|
||||
import flareIcon from '@/assets/img/app-icons/flare.webp'
|
||||
import flipTokIcon from '@/assets/img/app-icons/fliptok.webp'
|
||||
import picstagramIcon from '@/assets/img/app-icons/picstagram.webp'
|
||||
import skyRideIcon from '@/assets/img/app-icons/skyride.svg'
|
||||
import musicIcon from '@/assets/img/app-icons/music.svg'
|
||||
import featherIcon from '@/assets/img/app-icons/feather.svg'
|
||||
import crewLinkIcon from '@/assets/img/app-icons/crewlink.svg'
|
||||
import companiesIcon from '@/assets/img/app-icons/companies.svg'
|
||||
import weazelNewsIcon from '@/assets/img/app-icons/weazel-news.svg'
|
||||
import skyRideIcon from '@/assets/img/app-icons/skyride.webp'
|
||||
import musicIcon from '@/assets/img/app-icons/music.webp'
|
||||
import featherIcon from '@/assets/img/app-icons/feather.webp'
|
||||
import crewLinkIcon from '@/assets/img/app-icons/crewlink.webp'
|
||||
import companiesIcon from '@/assets/img/app-icons/companies.webp'
|
||||
import weazelNewsIcon from '@/assets/img/app-icons/weazel-news.webp'
|
||||
import cityWarnIcon from '@/assets/img/app-icons/citywarn.webp'
|
||||
import type {
|
||||
BuiltinPhoneAppDefinition,
|
||||
|
||||
@@ -11,18 +11,21 @@ export async function nuiCall<T = unknown>(
|
||||
endpoint: string,
|
||||
data: Record<string, unknown> = {},
|
||||
): Promise<NuiResponse<T>> {
|
||||
const developmentPort = new URLSearchParams(window.location.search).get(
|
||||
'apiPort',
|
||||
)
|
||||
const baseUrl = import.meta.env.DEV
|
||||
? `http://localhost:${developmentPort ?? '3002'}/api`
|
||||
: `https://${resourceName}`
|
||||
const requestData = import.meta.env.DEV
|
||||
const developmentParameters = new URLSearchParams(window.location.search)
|
||||
const developmentPort = developmentParameters.get('apiPort')
|
||||
const configuredApiBase = developmentParameters.get('apiBase')
|
||||
const browserApiBase = configuredApiBase?.startsWith('/')
|
||||
? configuredApiBase.replace(/\/$/, '')
|
||||
: null
|
||||
const baseUrl = browserApiBase
|
||||
?? (import.meta.env.DEV
|
||||
? `http://localhost:${developmentPort ?? '3002'}/api`
|
||||
: `https://${resourceName}`)
|
||||
const requestData = browserApiBase || import.meta.env.DEV
|
||||
? {
|
||||
...data,
|
||||
_testScenario:
|
||||
new URLSearchParams(window.location.search).get('testScenario') ??
|
||||
undefined,
|
||||
developmentParameters.get('testScenario') ?? undefined,
|
||||
}
|
||||
: data
|
||||
const controller = new AbortController()
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
WalletCards,
|
||||
} from 'lucide-vue-next'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import cryptoHeaderLogo from '@/assets/img/app-icons/crypto-header-logo.png'
|
||||
import cryptoHeaderLogo from '@/assets/img/app-icons/crypto-header-logo.webp'
|
||||
import CryptoLogo from '@/components/crypto/CryptoLogo.vue'
|
||||
import { useAccountStore } from '@/stores/account'
|
||||
import { useCryptoStore } from '@/stores/crypto'
|
||||
|
||||
@@ -2906,6 +2906,50 @@ let calendarEvents = [
|
||||
title: 'Beach cruise',
|
||||
},
|
||||
]
|
||||
const demoInstalledAppIds = [
|
||||
'citywarn',
|
||||
'crypto',
|
||||
'health',
|
||||
'weazel-news',
|
||||
'companies',
|
||||
'music',
|
||||
'picstagram',
|
||||
'feather',
|
||||
'fliptok',
|
||||
'flare',
|
||||
'calendar',
|
||||
'radio',
|
||||
'local-pages',
|
||||
'crewlink',
|
||||
'phone',
|
||||
'messages',
|
||||
'darkchat',
|
||||
'garage',
|
||||
'house',
|
||||
'map',
|
||||
'skyride',
|
||||
'banking',
|
||||
'billing',
|
||||
'mail',
|
||||
'notes',
|
||||
'memos',
|
||||
'calculator',
|
||||
'camera',
|
||||
'clock',
|
||||
'weather',
|
||||
'photos',
|
||||
'app-store',
|
||||
'settings',
|
||||
'snake',
|
||||
'memory',
|
||||
'number-merge',
|
||||
'minesweeper',
|
||||
'tower-stack',
|
||||
'sky-flappy',
|
||||
'citymarkt',
|
||||
'neon-drop',
|
||||
]
|
||||
const demoWallpaperUrl = '/img/wallpapers/sky-phone-demo-gradient.png'
|
||||
const deviceData = {
|
||||
appAuth: {
|
||||
payload: {
|
||||
@@ -2949,7 +2993,7 @@ const deviceData = {
|
||||
},
|
||||
apps: {
|
||||
payload: {
|
||||
claimedApps: [],
|
||||
claimedApps: [...demoInstalledAppIds],
|
||||
},
|
||||
revision: 2,
|
||||
},
|
||||
@@ -3042,6 +3086,8 @@ const deviceData = {
|
||||
airplaneMode: false,
|
||||
appearanceMode: 'automatic',
|
||||
frame: 'black',
|
||||
lockWallpaper: 'custom',
|
||||
lockWallpaperImageUrl: demoWallpaperUrl,
|
||||
notificationDurationSeconds: 10,
|
||||
notificationSound: 'chime',
|
||||
notificationVolume: 70,
|
||||
@@ -3049,7 +3095,11 @@ const deviceData = {
|
||||
ringtone: 'skyline',
|
||||
ringtoneVolume: 80,
|
||||
streamerMode: false,
|
||||
wallpaper: 'midnight',
|
||||
wallpaper: 'custom',
|
||||
wallpaperHistory: [
|
||||
{ imageUrl: demoWallpaperUrl, wallpaper: 'custom' },
|
||||
],
|
||||
wallpaperImageUrl: demoWallpaperUrl,
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
@@ -9716,7 +9766,33 @@ app.post('/api/:endpoint', (request, response) => {
|
||||
},
|
||||
},
|
||||
}
|
||||
: deviceData
|
||||
: testScenario === ''
|
||||
? {
|
||||
...deviceData,
|
||||
apps: {
|
||||
...deviceData.apps,
|
||||
payload: {
|
||||
claimedApps: [...demoInstalledAppIds],
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
...deviceData.settings,
|
||||
payload: {
|
||||
...deviceData.settings.payload,
|
||||
settings: {
|
||||
...deviceData.settings.payload.settings,
|
||||
lockWallpaper: 'custom',
|
||||
lockWallpaperImageUrl: demoWallpaperUrl,
|
||||
wallpaper: 'custom',
|
||||
wallpaperHistory: [
|
||||
{ imageUrl: demoWallpaperUrl, wallpaper: 'custom' },
|
||||
],
|
||||
wallpaperImageUrl: demoWallpaperUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: deviceData
|
||||
response.json({
|
||||
success: true,
|
||||
data: {
|
||||
|
||||
@@ -109,6 +109,31 @@ function verifyBrowserTestData(dataByEndpoint) {
|
||||
'camera captures',
|
||||
2,
|
||||
)
|
||||
expectItems(
|
||||
development.device.data.apps.payload.claimedApps,
|
||||
'installed demo apps',
|
||||
41,
|
||||
)
|
||||
assert.equal(
|
||||
new Set(development.device.data.apps.payload.claimedApps).size,
|
||||
41,
|
||||
'browser demo apps were not uniquely installed',
|
||||
)
|
||||
assert.equal(
|
||||
development.device.data.settings.payload.settings.wallpaper,
|
||||
'custom',
|
||||
'browser demo home wallpaper was not configured',
|
||||
)
|
||||
assert.equal(
|
||||
development.device.data.settings.payload.settings.lockWallpaper,
|
||||
'custom',
|
||||
'browser demo Lock Screen wallpaper was not configured',
|
||||
)
|
||||
assert.match(
|
||||
development.device.data.settings.payload.settings.wallpaperImageUrl,
|
||||
/sky-phone-demo-gradient\.png$/,
|
||||
'browser demo wallpaper image was not configured',
|
||||
)
|
||||
expectItems(development.memos, 'memos', 3)
|
||||
expectItems(development.notes, 'notes', 4)
|
||||
assert(
|
||||
|
||||