web: add notch easter egg & optimize for landscape

it took way too much time to optimize the damn logo sticker under notch for all devices & zoom states

also improved device lib api
This commit is contained in:
wukko 2024-07-03 19:05:14 +06:00
parent 901f0a7480
commit 374611553b
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
7 changed files with 164 additions and 16 deletions

View file

@ -0,0 +1,101 @@
<script lang="ts">
import { onMount } from "svelte";
import CobaltSticker from "$lib/icons/CobaltSticker.svelte";
// please add a source link (https://github.com/imputnet/cobalt) if you use this implementation
// i spent 4 hours switching between simulators and devices to get the best way to do this
$: safeAreaTop = 0;
$: state = "hidden"; // "notch", "island", "notch x"
const islandValues = [
59, // regular & plus: default
48, // regular: larger text
51, // plus only: larger text
];
const xNotch = [44];
const getSafeAreaTop = () => {
const root = document.documentElement;
return getComputedStyle(root)
.getPropertyValue("--safe-area-inset-top")
.trim();
};
onMount(() => {
safeAreaTop = Number(getSafeAreaTop().replace("px", ""));
});
$: if (safeAreaTop > 20) {
state = "notch";
if (islandValues.includes(safeAreaTop)) {
state = "island";
}
if (xNotch.includes(safeAreaTop)) {
state = "notch x";
}
}
</script>
<div id="cobalt-notch-sticker" aria-hidden="true" class={state}>
<CobaltSticker />
</div>
<style>
#cobalt-notch-sticker {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
width: 100%;
z-index: 999;
}
#cobalt-notch-sticker.hidden {
display: none;
}
#cobalt-notch-sticker.island {
padding-top: 15px;
}
#cobalt-notch-sticker.notch {
padding-top: 2px;
}
#cobalt-notch-sticker.notch.x :global(svg) {
height: 28px;
}
#cobalt-notch-sticker :global(svg) {
width: 100px;
height: 30px;
}
/* regular iphone size, larger text display mode */
@media screen and (max-width: 350px) {
#cobalt-notch-sticker.notch :global(svg) {
height: 24px;
}
#cobalt-notch-sticker.island {
padding-top: 9px;
}
}
/* plus iphone size, dynamic island, larger text display mode */
@media screen and (max-width: 375px) {
#cobalt-notch-sticker.island {
padding-top: 11px;
}
}
@media screen and (orientation: landscape) {
#cobalt-notch-sticker {
display: none;
}
}
</style>

View file

@ -2,7 +2,7 @@
import '@fontsource-variable/noto-sans-mono'; import '@fontsource-variable/noto-sans-mono';
import API from "$lib/api"; import API from "$lib/api";
import device from '$lib/device'; import { device } from '$lib/device';
export let url: string; export let url: string;
@ -35,7 +35,7 @@
} }
const downloadFile = (url: string) => { const downloadFile = (url: string) => {
if (device.isIOS) { if (device.is.iOS) {
return navigator?.share({ url }).catch(() => {}); return navigator?.share({ url }).catch(() => {});
} else { } else {
return window.open(url, '_blank'); return window.open(url, '_blank');

View file

@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import { t } from "$lib/i18n/translations"; import { t } from "$lib/i18n/translations";
import { device } from "$lib/device";
import CobaltLogo from "$components/sidebar/CobaltLogo.svelte"; import CobaltLogo from "$components/sidebar/CobaltLogo.svelte";
import SidebarTab from "$components/sidebar/SidebarTab.svelte"; import SidebarTab from "$components/sidebar/SidebarTab.svelte";
@ -25,7 +27,7 @@
<svelte:window bind:innerWidth={screenWidth} /> <svelte:window bind:innerWidth={screenWidth} />
<nav id="sidebar" aria-label={$t("a11y.tabs.tabPanel")}> <nav id="sidebar" aria-label={$t("a11y.tabs.tabPanel")} class:on-iPhone={device.is.iPhone}>
<CobaltLogo /> <CobaltLogo />
<div id="sidebar-tabs"> <div id="sidebar-tabs">
<div id="sidebar-actions" class="sidebar-inner-container"> <div id="sidebar-actions" class="sidebar-inner-container">
@ -114,4 +116,11 @@
padding-right: calc(var(--border-radius) * 2); padding-right: calc(var(--border-radius) * 2);
} }
} }
/* add padding for notch / dynamic island in landscape */
@media screen and (orientation: landscape) {
#sidebar.on-iPhone {
padding-left: env(safe-area-inset-left);
}
}
</style> </style>

View file

@ -1,17 +1,32 @@
const ua = navigator.userAgent.toLowerCase(); const ua = navigator.userAgent.toLowerCase();
const isIOS = ua.includes("iphone os") || (ua.includes("mac os") && navigator.maxTouchPoints > 0); const iPad = ua.includes("mac os") && navigator.maxTouchPoints > 0;
const isAndroid = ua.includes("android") || ua.includes("diordna"); const iPhone = ua.includes("iphone os");
const isMobile = isIOS || isAndroid;
const iOS = iPhone || iPad;
const android = ua.includes("android") || ua.includes("diordna");
const mobile = iOS || android;
const preferredLocale = navigator.language.toLowerCase().slice(0, 2); const preferredLocale = navigator.language.toLowerCase().slice(0, 2);
const device = { const installed = window.matchMedia('(display-mode: standalone)').matches;
isIOS,
isAndroid,
isMobile,
const device = {
is: {
iPad,
iPhone,
iOS,
android,
mobile,
},
preferredLocale, preferredLocale,
} }
export default device; const app = {
is: {
installed
}
}
export { device, app };

View file

@ -0,0 +1,6 @@
<svg width="236" height="70" viewBox="0 0 236 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="235.5" height="70" rx="35" fill="black"/>
<path d="M35 47.5683V42.9981L50.7927 35.4827L35 27.0024V22.4321L56.4293 34.1624V37.0061L35 47.5683Z" fill="white"/>
<path d="M53.5713 47.5683V42.9981L69.3639 35.4827L53.5713 27.0024V22.4321L75.0006 34.1624V37.0061L53.5713 47.5683Z" fill="white"/>
<path d="M102.105 45.86C100.945 45.86 99.9052 45.67 98.9852 45.29C98.0852 44.91 97.3152 44.37 96.6752 43.67C96.0552 42.97 95.5752 42.12 95.2352 41.12C94.9152 40.12 94.7552 39 94.7552 37.76C94.7552 36.52 94.9152 35.4 95.2352 34.4C95.5752 33.4 96.0552 32.55 96.6752 31.85C97.3152 31.15 98.0852 30.61 98.9852 30.23C99.9052 29.85 100.935 29.66 102.075 29.66C103.675 29.66 104.955 30 105.915 30.68C106.895 31.36 107.625 32.24 108.105 33.32L105.555 34.7C105.275 33.98 104.845 33.41 104.265 32.99C103.705 32.57 102.975 32.36 102.075 32.36C100.835 32.36 99.8752 32.74 99.1952 33.5C98.5152 34.24 98.1752 35.23 98.1752 36.47V39.05C98.1752 40.27 98.5152 41.26 99.1952 42.02C99.8752 42.78 100.855 43.16 102.135 43.16C103.095 43.16 103.875 42.94 104.475 42.5C105.095 42.06 105.585 41.46 105.945 40.7L108.345 42.17C107.845 43.25 107.085 44.14 106.065 44.84C105.045 45.52 103.725 45.86 102.105 45.86ZM119.523 45.86C118.383 45.86 117.353 45.67 116.433 45.29C115.533 44.91 114.763 44.37 114.123 43.67C113.503 42.97 113.023 42.12 112.683 41.12C112.343 40.12 112.173 39 112.173 37.76C112.173 36.52 112.343 35.4 112.683 34.4C113.023 33.4 113.503 32.55 114.123 31.85C114.763 31.15 115.533 30.61 116.433 30.23C117.353 29.85 118.383 29.66 119.523 29.66C120.663 29.66 121.683 29.85 122.583 30.23C123.503 30.61 124.273 31.15 124.893 31.85C125.533 32.55 126.023 33.4 126.363 34.4C126.703 35.4 126.873 36.52 126.873 37.76C126.873 39 126.703 40.12 126.363 41.12C126.023 42.12 125.533 42.97 124.893 43.67C124.273 44.37 123.503 44.91 122.583 45.29C121.683 45.67 120.663 45.86 119.523 45.86ZM119.523 43.28C120.723 43.28 121.673 42.92 122.373 42.2C123.093 41.46 123.453 40.37 123.453 38.93V36.59C123.453 35.15 123.093 34.07 122.373 33.35C121.673 32.61 120.723 32.24 119.523 32.24C118.323 32.24 117.363 32.61 116.643 33.35C115.943 34.07 115.593 35.15 115.593 36.59V38.93C115.593 40.37 115.943 41.46 116.643 42.2C117.363 42.92 118.323 43.28 119.523 43.28ZM130.882 23.3H134.152V32.66H134.332C134.792 31.72 135.402 30.99 136.162 30.47C136.922 29.93 137.872 29.66 139.012 29.66C140.832 29.66 142.282 30.34 143.362 31.7C144.442 33.06 144.982 35.08 144.982 37.76C144.982 40.44 144.442 42.46 143.362 43.82C142.282 45.18 140.832 45.86 139.012 45.86C137.872 45.86 136.922 45.6 136.162 45.08C135.402 44.54 134.792 43.8 134.332 42.86H134.152V45.5H130.882V23.3ZM137.662 43.19C138.902 43.19 139.852 42.81 140.512 42.05C141.192 41.29 141.532 40.28 141.532 39.02V36.5C141.532 35.24 141.192 34.23 140.512 33.47C139.852 32.71 138.902 32.33 137.662 32.33C137.182 32.33 136.732 32.39 136.312 32.51C135.892 32.63 135.522 32.81 135.202 33.05C134.882 33.29 134.622 33.59 134.422 33.95C134.242 34.29 134.152 34.7 134.152 35.18V40.34C134.152 40.82 134.242 41.24 134.422 41.6C134.622 41.94 134.882 42.23 135.202 42.47C135.522 42.71 135.892 42.89 136.312 43.01C136.732 43.13 137.182 43.19 137.662 43.19ZM161.26 45.5C160.32 45.5 159.61 45.26 159.13 44.78C158.67 44.3 158.39 43.67 158.29 42.89H158.14C157.84 43.83 157.29 44.56 156.49 45.08C155.71 45.6 154.71 45.86 153.49 45.86C151.91 45.86 150.65 45.45 149.71 44.63C148.77 43.79 148.3 42.64 148.3 41.18C148.3 39.68 148.85 38.54 149.95 37.76C151.07 36.96 152.78 36.56 155.08 36.56H158.05V35.36C158.05 33.26 156.94 32.21 154.72 32.21C153.72 32.21 152.91 32.41 152.29 32.81C151.67 33.19 151.15 33.7 150.73 34.34L148.78 32.75C149.22 31.91 149.96 31.19 151 30.59C152.04 29.97 153.38 29.66 155.02 29.66C156.98 29.66 158.52 30.13 159.64 31.07C160.76 32.01 161.32 33.37 161.32 35.15V42.92H163.27V45.5H161.26ZM154.42 43.46C155.48 43.46 156.35 43.22 157.03 42.74C157.71 42.26 158.05 41.64 158.05 40.88V38.63H155.14C152.78 38.63 151.6 39.34 151.6 40.76V41.36C151.6 42.06 151.85 42.59 152.35 42.95C152.85 43.29 153.54 43.46 154.42 43.46ZM166.738 42.86H171.868V25.94H166.738V23.3H175.138V42.86H180.268V45.5H166.738V42.86ZM192.407 45.5C191.047 45.5 190.047 45.14 189.407 44.42C188.767 43.68 188.447 42.73 188.447 41.57V32.66H183.677V30.02H187.067C187.647 30.02 188.057 29.91 188.297 29.69C188.537 29.45 188.657 29.03 188.657 28.43V24.56H191.717V30.02H198.317V32.66H191.717V42.86H198.317V45.5H192.407Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -2,20 +2,24 @@
import "@fontsource/ibm-plex-mono/400.css"; import "@fontsource/ibm-plex-mono/400.css";
import "@fontsource/ibm-plex-mono/500.css"; import "@fontsource/ibm-plex-mono/500.css";
import device from "$lib/device"; import { device, app } from "$lib/device";
import currentTheme, { statusBarColors } from "$lib/state/theme"; import currentTheme, { statusBarColors } from "$lib/state/theme";
import Sidebar from "$components/sidebar/Sidebar.svelte"; import Sidebar from "$components/sidebar/Sidebar.svelte";
import NotchSticker from "$components/misc/NotchSticker.svelte";
</script> </script>
<svelte:head> <svelte:head>
{#if device.isMobile} {#if device.is.mobile}
<meta name="theme-color" content={statusBarColors[$currentTheme]}> <meta name="theme-color" content={statusBarColors[$currentTheme]}>
{/if} {/if}
</svelte:head> </svelte:head>
<div style="display: contents" data-theme={$currentTheme}> <div style="display: contents" data-theme={$currentTheme}>
<div id="cobalt"> <div id="cobalt" class:on-iPhone={device.is.iPhone}>
{#if device.is.iPhone && app.is.installed}
<NotchSticker />
{/if}
<Sidebar /> <Sidebar />
<div id="content"> <div id="content">
<slot></slot> <slot></slot>
@ -57,6 +61,8 @@
--sidebar-font-size: 11px; --sidebar-font-size: 11px;
--sidebar-inner-padding: 4px; --sidebar-inner-padding: 4px;
--safe-area-inset-top: env(safe-area-inset-top);
--switcher-padding: var(--sidebar-inner-padding); --switcher-padding: var(--sidebar-inner-padding);
--sidebar-mobile-gradient: linear-gradient( --sidebar-mobile-gradient: linear-gradient(
@ -121,6 +127,18 @@
color: var(--secondary); color: var(--secondary);
} }
/* add padding for notch / dynamic island in landscape */
@media screen and (orientation: landscape) {
#cobalt.on-iPhone {
grid-template-columns:
calc(var(--sidebar-width) + env(safe-area-inset-left) + 8px) 1fr;
}
#cobalt.on-iPhone #content {
padding-right: env(safe-area-inset-right);
}
}
#content { #content {
display: flex; display: flex;
overflow: scroll; overflow: scroll;

View file

@ -9,14 +9,13 @@ import type { Load } from '@sveltejs/kit';
import languages from '$i18n/languages.json'; import languages from '$i18n/languages.json';
import { loadTranslations, defaultLocale } from '$lib/i18n/translations'; import { loadTranslations, defaultLocale } from '$lib/i18n/translations';
import device from '$lib/device.js';
export const load: Load = async ({ url }) => { export const load: Load = async ({ url }) => {
const { pathname } = url; const { pathname } = url;
let preferredLocale = defaultLocale; let preferredLocale = defaultLocale;
if (browser) { if (browser) {
const device = (await import('$lib/device')).device;
const settings = get((await import('$lib/settings')).default); const settings = get((await import('$lib/settings')).default);
const deviceLanguage = device.preferredLocale; const deviceLanguage = device.preferredLocale;
const settingsLanguage = settings.appearance.language; const settingsLanguage = settings.appearance.language;