From 87ae9902bfda1d0748c278480f8dbb4c3084118f Mon Sep 17 00:00:00 2001 From: DerEchteAlec Date: Wed, 12 Aug 2026 18:59:48 +0200 Subject: [PATCH] FIX - add Chrome 103 rendering fallbacks --- README.md | 39 ++++++++--- frontend/src/assets/main.css | 68 +++++++++++++------ frontend/src/components/CustomAppFrame.vue | 2 + .../src/components/MessageContactBubble.vue | 1 + frontend/src/components/SharedContentCard.vue | 1 + .../components/feather/FeatherPostCard.vue | 11 +++ frontend/src/utils/preferences.test.ts | 11 +++ frontend/src/utils/preferences.ts | 18 +++-- frontend/src/views/apps/BillingApp.vue | 25 +++++++ frontend/src/views/apps/NotesApp.vue | 3 +- frontend/src/views/apps/NumberMergeApp.vue | 55 +++++++++++---- frontend/src/views/apps/PhoneApp.vue | 2 +- frontend/src/views/apps/PicstagramApp.vue | 6 ++ 13 files changed, 188 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c8019bf..4b8324a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ # sky_phone +## Payphones + +Payphone dialing is validated against server-owned booth positions. Vanilla GTA V positions live +in `sky_phone/config/payphones.lua`; the server compares its own player coordinates with this list +and does not trust coordinates or models sent by the NUI or client. + +For a custom map or MLO, add each booth to `Config.Payphones.Locations` in that server-only file: + +```lua +{ model = "prop_phonebox_01a", coords = { x = 123.45, y = 678.90, z = 21.0 } }, +``` + +The model must also be present in `Config.Payphones.Props`. Coordinates must be finite Lua numbers +inside the supported world bounds. Restart `sky_phone` after changing the list. If payphones are +enabled but the list is empty or contains invalid entries, the server logs a visible warning and +rejects calls that cannot be matched to a valid configured location. + ## Custom Apps `sky_phone` erkennt Registrierungen gestarteter Fremd-App-Ressourcen über integrierte @@ -167,23 +184,25 @@ and restart the resource after updating the configuration. - When `Config.Sim.Enabled = true`, two unique, non-stackable inventory items named `sky_phone_sim_registered` and `sky_phone_sim_anonymous`. Their metadata is initialized automatically on first use, so shops and crafting recipes add plain items without supplying a number. These item definitions are not required when SIM cards are disabled. - `oxmysql` with MySQL/MariaDB. - `pma-voice` when `Config.Calls.VoiceProvider` is set to `"pma"`. -- A FiveManage V3 Media API token for Camera photo/video uploads and Gallery deletion. Set the - server-only `Config.Media.FiveManage.ApiKey` in `sky_phone/config/media.lua`; the token is never - sent to NUI because clients receive temporary presigned upload URLs instead. +- A FiveManage V3 Media API token for Camera photo/video uploads and Gallery deletion. Set it as a + server-only convar; the token is never sent to NUI because clients receive temporary presigned + upload URLs instead: + +```cfg +set sky_phone_fivemanage_api_key "replace-with-your-media-token" +``` - `yaca-voice`, `pma-voice`, or `saltychat` when the Radio app is enabled. `Config.Radio.VoiceProvider = "auto"` selects the first running provider in that order. ## Messages GIF provider -Configure GIF search in `sky_phone/config/config.lua`: +Configure GIF search as a server-only convar: -```lua -Config.Media.GiphyApiKey = "YOUR_GIPHY_API_KEY" +```cfg +set sky_phone_giphy_api_key "replace-with-your-giphy-api-key" ``` -GIPHY provides trending and searched GIFs through a paginated server-side proxy. The shared -`config.lua` is loaded by both FiveM runtimes, so its values are available to clients even though -only the server uses the GIPHY key. Photo and video actions in Messages are intentionally inactive -until their dedicated implementation is available. +GIPHY provides trending and searched GIFs through a paginated server-side proxy. Only the server +reads the key. Photo and video actions in Messages use media captured by the Camera app. Database migrations run automatically. Existing `sky_phone_mail_accounts` installations are renamed to `sky_phone_accounts` while preserving account IDs and mail foreign keys. The migration also creates `sky_phone_character_devices` for persistent non-unique phone mappings and marks automatic SIMs through `sky_phone_sims.is_virtual`. iFruit passwords are intentional in-character credentials and remain plaintext `VARCHAR(64)` values; registration screens warn players never to reuse a real password. diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 10e108c..2769c81 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -14,9 +14,13 @@ .sim-picker { position: relative; width: min(58vh, 90vw); - max-height: 46vh; - overflow: clip; + max-width: calc(100vw - 32px); + max-height: calc(100vh - 32px); + min-height: 0; + overflow: hidden; padding: 2.2vh; + display: flex; + flex-direction: column; border: 0.1vh solid rgb(255 255 255 / 12%); border-radius: 0.9vh; background: #050505; @@ -56,11 +60,16 @@ .sim-picker__header { display: flex; + flex: 0 0 auto; justify-content: space-between; gap: 2vh; margin-bottom: 1.6vh; } +.sim-picker__header > div { + min-width: 0; +} + .sim-picker__header h1, .sim-picker__confirmation h2 { margin: 0; @@ -73,6 +82,7 @@ margin: 0.45vh 0 0; color: #9ca3af; font-size: 1.15vh; + overflow-wrap: anywhere; } .sim-picker__close { @@ -80,6 +90,8 @@ place-items: center; width: 2.8vh; height: 2.8vh; + min-width: 32px; + min-height: 32px; border: 0; border-radius: 0.35vh; background: #1dd1ce; @@ -96,10 +108,12 @@ position: relative; z-index: 1; display: grid; + flex: 1 1 auto; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1vh; - max-height: 34vh; - overflow: auto; + min-height: 0; + max-height: 54vh; + overflow-y: auto; } .sim-picker__card { @@ -140,30 +154,34 @@ } .sim-picker__details strong { - overflow: hidden; font-size: 1.2vh; - text-overflow: ellipsis; - white-space: nowrap; + overflow-wrap: anywhere; } .sim-picker__details small { color: #9ca3af; font-size: 0.95vh; + overflow-wrap: anywhere; } .sim-picker__confirmation { + min-height: 0; + overflow-y: auto; padding: 2vh 0 0; text-align: center; } .sim-picker__confirmation > div { display: flex; + flex-wrap: wrap; justify-content: center; gap: 1vh; margin-top: 2vh; } .sim-picker__confirmation button { + min-height: 40px; + overflow-wrap: anywhere; padding: 0.8vh 1.6vh; border: 0.1vh solid rgb(255 255 255 / 18%); border-radius: 0.45vh; @@ -184,6 +202,13 @@ color: #ff453a; font-size: 1vh; text-align: center; + overflow-wrap: anywhere; +} + +@media (max-width: 640px) { + .sim-picker__cards { + grid-template-columns: 1fr; + } } :root { font-family: @@ -205,6 +230,7 @@ body, margin: 0; overflow: hidden; background: transparent !important; + pointer-events: none; user-select: none; } button, @@ -885,13 +911,13 @@ button { } /* DarkChat is intentionally independent from the phone appearance setting. */ -.phone-app:has(.darkchat-page), -.phone-app:has(.darkchat-page) .phone-app-window, -.phone-app:has(.darkchat-page) .phone-app-window__content { +.phone-app--darkchat, +.phone-app--darkchat .phone-app-window, +.phone-app--darkchat .phone-app-window__content { background: #000 !important; color-scheme: dark; } -.phone-app:has(.darkchat-page) .phone-status-bar { +.phone-app--darkchat .phone-status-bar { color: #fff !important; --status-bar-color: #fff; } @@ -1592,7 +1618,7 @@ button { font-size: 9px; white-space: nowrap; } -.darkchat-message:has(.darkchat-reactions) { +.darkchat-message { margin-bottom: 12px; } .darkchat-replying { @@ -3714,7 +3740,7 @@ button { .clock-world-time { margin-top: 8px; font-variant-numeric: tabular-nums; - font-size: min(76px, 20cqw); + font-size: 74px; font-weight: 200; letter-spacing: -4px; } @@ -3861,7 +3887,7 @@ button { .clock-digits { margin: 70px 0 54px; font-variant-numeric: tabular-nums; - font-size: min(52px, 14cqw); + font-size: 52px; font-weight: 200; } .clock-sound-menu { @@ -4242,11 +4268,11 @@ button { background: #fff !important; color: #111; } -.phone-app:has(.messages-page) .phone-status-bar { +.phone-app--messages .phone-status-bar { color: #080808; text-shadow: none; } -.phone-app:has(.messages-page)::before { +.phone-app--messages::before { content: ''; position: absolute; z-index: 60; @@ -5458,7 +5484,7 @@ button { } } /* iOS 26-style Liquid Glass refinements and complete media pickers. */ -.phone-app:has(.messages-page) { +.messages-page { --ios-glass: rgb(252 252 255 / 70%); --ios-glass-strong: rgb(255 255 255 / 86%); --ios-glass-border: rgb(255 255 255 / 72%); @@ -5779,21 +5805,21 @@ button { .messages-attachment--video small { z-index: 2; } -.messages-attachment--gif:has(img) { +.messages-attachment--gif { background: #e9e9ed; } /* Konsta's k-app dark state drives the complete iOS Messages palette. */ -.phone-app.dark:has(.messages-page) { +.phone-app.dark .messages-page { --ios-glass: rgb(28 28 30 / 76%); --ios-glass-strong: rgb(36 36 38 / 90%); --ios-glass-border: rgb(255 255 255 / 12%); color-scheme: dark; } -.phone-app.dark:has(.messages-page)::before { +.phone-app--messages.dark::before { background: #000; } -.phone-app.dark:has(.messages-page) .phone-status-bar { +.phone-app--messages.dark .phone-status-bar { color: #fff; text-shadow: none; } diff --git a/frontend/src/components/CustomAppFrame.vue b/frontend/src/components/CustomAppFrame.vue index bc6d973..135be23 100644 --- a/frontend/src/components/CustomAppFrame.vue +++ b/frontend/src/components/CustomAppFrame.vue @@ -447,6 +447,8 @@ watch(() => catalog.openRequests[props.app.id], flushOpenRequest, { right: auto; bottom: auto; left: 50%; + width: 827px; + height: 368px; width: 100cqh; height: 100cqw; transform: translate(-50%, -50%) rotate(90deg); diff --git a/frontend/src/components/MessageContactBubble.vue b/frontend/src/components/MessageContactBubble.vue index 0ee78bc..e50fba7 100644 --- a/frontend/src/components/MessageContactBubble.vue +++ b/frontend/src/components/MessageContactBubble.vue @@ -81,6 +81,7 @@ const initials = computed(() => diff --git a/frontend/src/utils/preferences.test.ts b/frontend/src/utils/preferences.test.ts index 9ddfb58..5084e7d 100644 --- a/frontend/src/utils/preferences.test.ts +++ b/frontend/src/utils/preferences.test.ts @@ -85,6 +85,17 @@ describe('preferences', () => { expect(value.settings.screenBrightness).toBe(10) }) + it('keeps the phone above the minimum usable scale', () => { + const value = parsePhonePreferences( + JSON.stringify({ + version: 1, + settings: { phoneScale: 50 }, + }), + ) + + expect(value.settings.phoneScale).toBe(75) + }) + it('preserves safe notification preferences for custom apps', () => { const appId = 'example-app' as LaunchablePhoneAppId const value = parsePhonePreferences( diff --git a/frontend/src/utils/preferences.ts b/frontend/src/utils/preferences.ts index 7ef0550..c586705 100644 --- a/frontend/src/utils/preferences.ts +++ b/frontend/src/utils/preferences.ts @@ -23,7 +23,7 @@ export const PHONE_FRAME_IDS = [ export const RINGTONE_IDS = ['skyline', 'horizon', 'pulse'] as const export const NOTIFICATION_SOUND_IDS = ['chime', 'signal', 'soft'] as const export const WALLPAPER_IDS = ['midnight', 'aurora', 'ember'] as const -export const PHONE_SCALE_MIN = 50 +export const PHONE_SCALE_MIN = 75 export const PHONE_SCALE_MAX = 150 export const PHONE_SCALE_STEP = 5 @@ -197,6 +197,10 @@ export function ensureAppNotificationPreferences( } } +export function clampPhoneScale(value: number): number { + return Math.min(PHONE_SCALE_MAX, Math.max(PHONE_SCALE_MIN, value)) +} + export function parsePhonePreferences(raw: string | null): PhonePreferencesV1 { if (!raw) return cloneJsonData(DEFAULT_PHONE_PREFERENCES) @@ -249,11 +253,13 @@ export function parsePhonePreferences(raw: string | null): PhonePreferencesV1 { 100, ), notifications: readNotifications(settings.notifications), - phoneScale: readNumber( - settings.phoneScale, - defaults.phoneScale, - PHONE_SCALE_MIN, - PHONE_SCALE_MAX, + phoneScale: clampPhoneScale( + readNumber( + settings.phoneScale, + defaults.phoneScale, + Number.MIN_SAFE_INTEGER, + Number.MAX_SAFE_INTEGER, + ), ), ringtone: readChoice( settings.ringtone, diff --git a/frontend/src/views/apps/BillingApp.vue b/frontend/src/views/apps/BillingApp.vue index d290b24..e637b63 100644 --- a/frontend/src/views/apps/BillingApp.vue +++ b/frontend/src/views/apps/BillingApp.vue @@ -1404,6 +1404,31 @@ onBeforeUnmount(() => { --k-button-bg-color: var(--billing-blue); width: 100%; } +@supports not (color: color-mix(in srgb, white, black)) { + .billing-navbar { + --k-navbar-bg-color: rgb(7 9 12 / 90%); + background: rgb(7 9 12 / 88%); + } + .billing-app--light .billing-navbar { + --k-navbar-bg-color: rgb(245 247 250 / 91%); + background: rgb(245 247 250 / 88%); + } + .billing-summary__item, + .billing-filter-panel, + .billing-invoice-card, + .billing-list-row, + .billing-search :deep(form), + .billing-detail__hero, + .billing-panel, + .billing-note { + background: var(--billing-panel); + } + .billing-detail__hero--paid { + background: + radial-gradient(circle at 50% 6%, rgb(72 199 111 / 18%), transparent 52%), + var(--billing-panel); + } +} .billing-toast { z-index: 50; } diff --git a/frontend/src/views/apps/NotesApp.vue b/frontend/src/views/apps/NotesApp.vue index 2a6c55b..a4c600e 100644 --- a/frontend/src/views/apps/NotesApp.vue +++ b/frontend/src/views/apps/NotesApp.vue @@ -50,7 +50,8 @@ const deleteActionColors = { textMaterial: 'text-red-500', } const noteBodyStyle: CSSProperties = { - height: 'calc(100cqh - 210px)', + height: '617px', + maxHeight: 'calc(100% - 210px)', resize: 'none', } const currentNote = computed(() => diff --git a/frontend/src/views/apps/NumberMergeApp.vue b/frontend/src/views/apps/NumberMergeApp.vue index c1d6cbf..897c649 100644 --- a/frontend/src/views/apps/NumberMergeApp.vue +++ b/frontend/src/views/apps/NumberMergeApp.vue @@ -348,6 +348,11 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) display: flex; align-items: center; justify-content: space-between; + gap: 10px; +} + +.number-merge-header > div { + min-width: 0; } .number-merge-header span { @@ -357,6 +362,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase; + overflow-wrap: anywhere; } .number-merge-header h1 { @@ -364,6 +370,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) font-size: 28px; line-height: 1; letter-spacing: -1px; + overflow-wrap: anywhere; } .number-merge-header button, @@ -383,11 +390,14 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) .number-merge-menu { height: calc(100% - 55px); + min-height: 0; display: flex; flex-direction: column; align-items: center; - justify-content: center; + justify-content: flex-start; gap: 13px; + overflow-y: auto; + padding: 12px 0; text-align: center; } @@ -401,6 +411,8 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) border-radius: 29px; background: #7e5147; box-shadow: 0 17px 30px rgb(93 48 36 / 20%); + flex: 0 0 auto; + margin-top: auto; transform: rotate(-2deg); } @@ -418,8 +430,9 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) .number-merge-hero__tile--4 { color: #fff7e4; background: #e96c2c; } .number-merge-hero__tile--8 { color: #fff7e4; background: #713c31; } -.number-merge-menu__intro h2 { margin: 0; font-size: 25px; letter-spacing: -0.5px; } -.number-merge-menu__intro p { max-width: 300px; margin: 6px 0 0; color: #8b675b; font-size: 14px; line-height: 1.45; } +.number-merge-menu__intro { width: 100%; } +.number-merge-menu__intro h2 { margin: 0; font-size: 25px; letter-spacing: -0.5px; overflow-wrap: anywhere; } +.number-merge-menu__intro p { max-width: 300px; margin: 6px auto 0; color: #8b675b; font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; } .number-merge-records { width: 100%; @@ -429,6 +442,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) } .number-merge-records div { + min-width: 0; display: grid; gap: 1px; padding: 8px; @@ -443,9 +457,10 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) font-size: 11px; font-weight: 800; text-transform: uppercase; + overflow-wrap: anywhere; } -.number-merge-records strong { font-size: 20px; } +.number-merge-records strong { min-width: 0; overflow-wrap: anywhere; font-size: 20px; } .number-merge-menu__actions { width: 100%; display: grid; gap: 7px; } @@ -453,11 +468,12 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) .number-merge-secondary, .number-merge-danger { min-height: 46px; - padding: 0 18px; + padding: 10px 18px; border-radius: 14px; font-size: 15px; font-weight: 850; cursor: pointer; + overflow-wrap: anywhere; } .number-merge-primary { @@ -474,14 +490,17 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) } .number-merge-how-to { + width: 100%; + flex: 0 0 auto; + margin-bottom: auto; padding: 9px 15px; border-radius: 14px; color: #795549; background: rgb(255 255 255 / 28%); } -.number-merge-how-to strong { font-size: 13px; text-transform: uppercase; } -.number-merge-how-to p { margin: 5px 0 0; font-size: 12px; line-height: 1.4; } +.number-merge-how-to strong { font-size: 13px; text-transform: uppercase; overflow-wrap: anywhere; } +.number-merge-how-to p { margin: 5px 0 0; font-size: 12px; line-height: 1.4; overflow-wrap: anywhere; } .number-merge-how-to div { margin: 7px 0 4px; color: #a7472d; @@ -489,7 +508,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) font-weight: 900; letter-spacing: -0.2px; } -.number-merge-how-to small { display: block; color: #8b6559; font-size: 11px; line-height: 1.35; } +.number-merge-how-to small { display: block; color: #8b6559; font-size: 11px; line-height: 1.35; overflow-wrap: anywhere; } .number-merge-game { position: absolute; @@ -629,7 +648,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) display: flex; flex-direction: column; align-items: center; - justify-content: center; + justify-content: flex-start; gap: 8px; padding: 22px; border-radius: 20px; @@ -637,12 +656,14 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) backdrop-filter: blur(5px); color: #fff7ea; text-align: center; + overflow-y: auto; } -.number-merge-overlay > span { color: #ffc75b; font-size: 29px; font-weight: 900; } -.number-merge-overlay h2 { margin: 0; font-size: 25px; } -.number-merge-overlay p { margin: -3px 0 4px; color: #e7cabd; font-size: 14px; line-height: 1.4; } -.number-merge-overlay button { min-width: 160px; } +.number-merge-overlay > span { margin-top: auto; color: #ffc75b; font-size: 29px; font-weight: 900; } +.number-merge-overlay h2 { margin: 0; font-size: 25px; overflow-wrap: anywhere; } +.number-merge-overlay p { margin: -3px 0 4px; color: #e7cabd; font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; } +.number-merge-overlay button { min-width: min(160px, 100%); } +.number-merge-overlay .number-merge-link { margin-bottom: auto; } .number-merge-overlay .number-merge-secondary { color: #fff1df; background: rgb(255 255 255 / 8%); border-color: rgb(255 255 255 / 13%); } .number-merge-link { @@ -677,6 +698,8 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) inset: 0; display: grid; place-items: center; + min-height: 0; + overflow-y: auto; padding: 30px; background: rgb(54 29 25 / 54%); backdrop-filter: blur(6px); @@ -684,6 +707,8 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) .number-merge-confirm > div { width: 100%; + max-height: calc(100% - 32px); + overflow-y: auto; display: grid; gap: 9px; padding: 21px; @@ -694,8 +719,8 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) text-align: center; } -.number-merge-confirm h2 { margin: 0; font-size: 21px; } -.number-merge-confirm p { margin: 0 0 5px; color: #876359; font-size: 14px; line-height: 1.4; } +.number-merge-confirm h2 { margin: 0; font-size: 21px; overflow-wrap: anywhere; } +.number-merge-confirm p { margin: 0 0 5px; color: #876359; font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; } .number-merge-danger { border: 0; color: #fff; background: #b64f39; } button:active { transform: scale(0.97); } diff --git a/frontend/src/views/apps/PhoneApp.vue b/frontend/src/views/apps/PhoneApp.vue index e0461be..49f69e1 100644 --- a/frontend/src/views/apps/PhoneApp.vue +++ b/frontend/src/views/apps/PhoneApp.vue @@ -3729,7 +3729,7 @@ onBeforeUnmount(() => { box-shadow: inset 0 0 0 1px rgba(142, 142, 147, 0.18); } - .phone-recent-row:has(button:hover), + .phone-recent-row:hover, .phone-my-card:hover, .phone-contact-row:hover { border-radius: 14px; diff --git a/frontend/src/views/apps/PicstagramApp.vue b/frontend/src/views/apps/PicstagramApp.vue index dc7a6d6..96070a0 100644 --- a/frontend/src/views/apps/PicstagramApp.vue +++ b/frontend/src/views/apps/PicstagramApp.vue @@ -2852,4 +2852,10 @@ onBeforeUnmount(() => { transform: scale(1.28); } } + +@supports not (color: color-mix(in srgb, white, black)) { + .ps-activity--unread { + background: rgb(10 132 255 / 10%); + } +}