mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 00:01:29 +00:00
ENH - refine phone apps and lock screen
This commit is contained in:
@@ -63,10 +63,7 @@ function getViewportScale(): number {
|
||||
const heightScale = window.innerHeight / REFERENCE_VIEWPORT_HEIGHT
|
||||
if (isDevelopment) return heightScale
|
||||
|
||||
return Math.min(
|
||||
window.innerWidth / REFERENCE_VIEWPORT_WIDTH,
|
||||
heightScale,
|
||||
)
|
||||
return Math.min(window.innerWidth / REFERENCE_VIEWPORT_WIDTH, heightScale)
|
||||
}
|
||||
|
||||
function onMessage(event: MessageEvent<AppMessage>): void {
|
||||
|
||||
@@ -115,6 +115,7 @@ button {
|
||||
color-scheme: light;
|
||||
}
|
||||
.phone-notification {
|
||||
top: 68px !important;
|
||||
right: 8px !important;
|
||||
left: 8px !important;
|
||||
width: auto !important;
|
||||
@@ -241,10 +242,18 @@ button {
|
||||
left: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-end;
|
||||
min-height: 18px;
|
||||
filter: drop-shadow(0 1px 2px #0009);
|
||||
}
|
||||
.lock-screen__lock {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 64px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
filter: drop-shadow(0 1px 2px #0009);
|
||||
}
|
||||
.lock-screen__indicators {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@@ -258,21 +267,26 @@ button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 80px;
|
||||
padding-top: 94px;
|
||||
text-shadow: 0 2px 14px #0005;
|
||||
}
|
||||
.lock-screen__date {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.35px;
|
||||
font-size: 16px;
|
||||
font-weight: 550;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
.lock-screen__time {
|
||||
margin-top: -8px;
|
||||
font-size: 92px;
|
||||
font-weight: 250;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -6px;
|
||||
margin-top: -5px;
|
||||
color: #ffffffaa;
|
||||
font-size: 110px;
|
||||
font-weight: 300;
|
||||
line-height: 1.04;
|
||||
letter-spacing: -7px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-shadow:
|
||||
0 0 12px #ffffff38,
|
||||
0 2px 16px #0006;
|
||||
filter: blur(0.24px);
|
||||
}
|
||||
.lock-screen__footer {
|
||||
position: absolute;
|
||||
@@ -281,35 +295,6 @@ button {
|
||||
bottom: 25px;
|
||||
left: 0;
|
||||
}
|
||||
.lock-screen__shortcuts {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 43px;
|
||||
}
|
||||
.lock-screen__shortcut {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff;
|
||||
background: #2424248f;
|
||||
backdrop-filter: blur(18px) saturate(135%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(135%);
|
||||
box-shadow: inset 0 0 0 1px #ffffff17;
|
||||
transition:
|
||||
transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
|
||||
color 0.18s,
|
||||
background 0.18s;
|
||||
}
|
||||
.lock-screen__shortcut:active {
|
||||
transform: scale(0.88);
|
||||
}
|
||||
.lock-screen__shortcut--active {
|
||||
color: #171717;
|
||||
background: #fff;
|
||||
}
|
||||
.lock-screen__swipe {
|
||||
width: 100%;
|
||||
margin-top: 18px;
|
||||
@@ -873,6 +858,7 @@ button {
|
||||
.calculator-display {
|
||||
height: 145px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
padding: 0 8px 12px;
|
||||
@@ -881,6 +867,20 @@ button {
|
||||
font-weight: 250;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.calculator-result {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.calculator-calculation {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
color: #8e8e93;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.calculator-pad {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
@@ -1078,15 +1078,6 @@ button {
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.clock-section-heading {
|
||||
margin: 0;
|
||||
padding: 4px 0 10px;
|
||||
text-align: left;
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
line-height: 41px;
|
||||
letter-spacing: -0.8px;
|
||||
}
|
||||
.clock-digits {
|
||||
margin: 70px 0 54px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
@@ -1095,9 +1086,11 @@ button {
|
||||
}
|
||||
.clock-sound-menu {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
max-height: calc(100% - 52px);
|
||||
flex: 0 1 auto;
|
||||
align-self: stretch;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 8px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.clock-sound-selection {
|
||||
@@ -1126,12 +1119,13 @@ button {
|
||||
}
|
||||
.timer-ring {
|
||||
position: relative;
|
||||
width: 170px;
|
||||
height: 170px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
margin: 12px auto 16px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 34px;
|
||||
font-size: 42px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.timer-ring svg {
|
||||
position: absolute;
|
||||
@@ -1142,7 +1136,7 @@ button {
|
||||
}
|
||||
.timer-ring circle {
|
||||
fill: none;
|
||||
stroke-width: 7;
|
||||
stroke-width: 4;
|
||||
}
|
||||
.timer-ring-track {
|
||||
stroke: #ff9f0a33;
|
||||
@@ -1162,9 +1156,12 @@ button {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
.clock-timer-sound {
|
||||
.clock-timer-note {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.clock-timer-sound {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.clock-timer {
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
@@ -1610,6 +1607,17 @@ button {
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #020202;
|
||||
color: #fff;
|
||||
}
|
||||
.phone-app--light .clock-app {
|
||||
background: #f2f2f7;
|
||||
color: #000;
|
||||
}
|
||||
.phone-app--light .time-wheel-picker__selection {
|
||||
background: #e5e5ea;
|
||||
}
|
||||
.phone-app--light .time-wheel-picker__value--selected {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.reference-photos {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { kLink, kNavbar } from 'konsta/vue'
|
||||
import {
|
||||
BatteryMedium,
|
||||
Camera,
|
||||
@@ -20,6 +21,12 @@ const now = ref(new Date())
|
||||
const dragOffset = ref(0)
|
||||
const dragging = ref(false)
|
||||
const flashlightActive = ref(false)
|
||||
const lockNavbarColors = { bgIos: 'bg-transparent' }
|
||||
const neutralGlassClass =
|
||||
'!bg-white/10 !shadow-none ring-1 ring-inset ring-white/15 backdrop-saturate-150'
|
||||
const activeFlashlightGlassClass =
|
||||
'!bg-white !shadow-none ring-1 ring-inset ring-white/60 backdrop-saturate-150'
|
||||
const whiteNavbarLinkColors = { navbarTextIos: 'text-white' }
|
||||
let pointerStart = 0
|
||||
let pointerStartedAt = 0
|
||||
let clockTicker: number | undefined
|
||||
@@ -45,6 +52,12 @@ const time = computed(() =>
|
||||
const dragStyle = computed(() => ({
|
||||
'--lock-drag': `${dragOffset.value}px`,
|
||||
}))
|
||||
const flashlightGlassClass = computed(() =>
|
||||
flashlightActive.value ? activeFlashlightGlassClass : neutralGlassClass,
|
||||
)
|
||||
const flashlightLinkColors = computed(() => ({
|
||||
navbarTextIos: flashlightActive.value ? 'text-purple-500' : 'text-white',
|
||||
}))
|
||||
|
||||
function onPointerDown(event: PointerEvent): void {
|
||||
if ((event.target as HTMLElement).closest('button')) return
|
||||
@@ -78,6 +91,11 @@ function unlockWithKeyboard(event: KeyboardEvent): void {
|
||||
if (event.key === 'Enter' || event.key === ' ') emit('unlock')
|
||||
}
|
||||
|
||||
function unlockFromWallpaper(event: MouseEvent): void {
|
||||
if ((event.target as HTMLElement).closest('button, [role="link"]')) return
|
||||
emit('unlock')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
clockTicker = window.setInterval(() => {
|
||||
now.value = new Date()
|
||||
@@ -104,17 +122,23 @@ onBeforeUnmount(() => {
|
||||
@pointermove="onPointerMove"
|
||||
@pointerup="finishPointer"
|
||||
@pointercancel="finishPointer"
|
||||
@click="unlockFromWallpaper"
|
||||
>
|
||||
<div class="lock-screen__shade" aria-hidden="true"></div>
|
||||
|
||||
<header class="lock-screen__status">
|
||||
<LockKeyhole :size="13" :stroke-width="2.5" aria-hidden="true" />
|
||||
<div class="lock-screen__indicators" aria-hidden="true">
|
||||
<Signal :size="12" :stroke-width="2.5" />
|
||||
<Wifi :size="13" :stroke-width="2.5" />
|
||||
<BatteryMedium :size="17" :stroke-width="2.4" />
|
||||
</div>
|
||||
</header>
|
||||
<LockKeyhole
|
||||
class="lock-screen__lock"
|
||||
:size="14"
|
||||
:stroke-width="1.8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div class="lock-screen__content">
|
||||
<time class="lock-screen__date">{{ date }}</time>
|
||||
@@ -122,31 +146,40 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
|
||||
<div class="lock-screen__footer">
|
||||
<div class="lock-screen__shortcuts">
|
||||
<button
|
||||
class="lock-screen__shortcut"
|
||||
:class="{ 'lock-screen__shortcut--active': flashlightActive }"
|
||||
type="button"
|
||||
:aria-label="phone.t('LockScreen.flashlight')"
|
||||
@click="flashlightActive = !flashlightActive"
|
||||
>
|
||||
<Flashlight :size="22" fill="currentColor" />
|
||||
</button>
|
||||
<button
|
||||
class="lock-screen__shortcut"
|
||||
type="button"
|
||||
:aria-label="phone.t('LockScreen.camera')"
|
||||
@click="emit('unlock', 'camera')"
|
||||
>
|
||||
<Camera :size="24" fill="currentColor" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="lock-screen__swipe"
|
||||
type="button"
|
||||
@click="emit('unlock')"
|
||||
<k-navbar
|
||||
transparent
|
||||
:colors="lockNavbarColors"
|
||||
inner-class="!px-12"
|
||||
:left-class="flashlightGlassClass"
|
||||
:right-class="neutralGlassClass"
|
||||
>
|
||||
<template #left>
|
||||
<k-link
|
||||
component="button"
|
||||
icon-only
|
||||
:colors="flashlightLinkColors"
|
||||
:link-props="{ type: 'button' }"
|
||||
:aria-label="phone.t('LockScreen.flashlight')"
|
||||
@click="flashlightActive = !flashlightActive"
|
||||
>
|
||||
<Flashlight :stroke-width="1.4" aria-hidden="true" />
|
||||
</k-link>
|
||||
</template>
|
||||
<template #right>
|
||||
<k-link
|
||||
component="button"
|
||||
icon-only
|
||||
:colors="whiteNavbarLinkColors"
|
||||
:link-props="{ type: 'button' }"
|
||||
:aria-label="phone.t('LockScreen.camera')"
|
||||
@click="emit('unlock', 'camera')"
|
||||
>
|
||||
<Camera :stroke-width="1.4" aria-hidden="true" />
|
||||
</k-link>
|
||||
</template>
|
||||
</k-navbar>
|
||||
|
||||
<button class="lock-screen__swipe" type="button" @click="emit('unlock')">
|
||||
<span class="lock-screen__swipe-chevron" aria-hidden="true"></span>
|
||||
{{ phone.t('LockScreen.swipeUp') }}
|
||||
</button>
|
||||
|
||||
@@ -17,11 +17,27 @@ describe('calculator', () => {
|
||||
it('chains operations and handles division by zero', () => {
|
||||
let state = inputDigit(clearCalculator(), '8')
|
||||
state = chooseCalculatorOperator(state, 'add')
|
||||
expect(state.calculation).toBe('8 +')
|
||||
state = inputDigit(state, '2')
|
||||
state = chooseCalculatorOperator(state, 'multiply')
|
||||
expect(state.display).toBe('10')
|
||||
expect(state.calculation).toBe('8 + 2 ×')
|
||||
state = inputDigit(state, '3')
|
||||
expect(resolveCalculator(state).display).toBe('30')
|
||||
state = resolveCalculator(state)
|
||||
expect(state.display).toBe('30')
|
||||
expect(state.calculation).toBe('8 + 2 × 3 =')
|
||||
expect(calculate(4, 0, 'divide')).toBeNull()
|
||||
})
|
||||
|
||||
it('replaces a pending operator and clears completed history on new input', () => {
|
||||
let state = inputDigit(clearCalculator(), '9')
|
||||
state = chooseCalculatorOperator(state, 'add')
|
||||
state = chooseCalculatorOperator(state, 'subtract')
|
||||
expect(state.calculation).toBe('9 −')
|
||||
|
||||
state = inputDigit(state, '3')
|
||||
state = resolveCalculator(state)
|
||||
expect(state.calculation).toBe('9 − 3 =')
|
||||
expect(inputDigit(state, '4').calculation).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ export type CalculatorOperator = 'add' | 'subtract' | 'multiply' | 'divide'
|
||||
|
||||
export type CalculatorState = {
|
||||
accumulator: number | null
|
||||
calculation: string
|
||||
display: string
|
||||
error: boolean
|
||||
pendingOperator: CalculatorOperator | null
|
||||
@@ -10,12 +11,20 @@ export type CalculatorState = {
|
||||
|
||||
export const INITIAL_CALCULATOR_STATE: CalculatorState = {
|
||||
accumulator: null,
|
||||
calculation: '',
|
||||
display: '0',
|
||||
error: false,
|
||||
pendingOperator: null,
|
||||
waitingForOperand: false,
|
||||
}
|
||||
|
||||
const OPERATOR_SYMBOLS: Record<CalculatorOperator, string> = {
|
||||
add: '+',
|
||||
subtract: '−',
|
||||
multiply: '×',
|
||||
divide: '÷',
|
||||
}
|
||||
|
||||
function formatNumber(value: number): string {
|
||||
if (!Number.isFinite(value)) return 'Error'
|
||||
const rounded = Number(value.toPrecision(12))
|
||||
@@ -45,7 +54,13 @@ export function inputDigit(
|
||||
): CalculatorState {
|
||||
if (!/^\d$/.test(digit)) return state
|
||||
if (state.error || state.waitingForOperand) {
|
||||
return { ...state, display: digit, error: false, waitingForOperand: false }
|
||||
return {
|
||||
...state,
|
||||
calculation: state.pendingOperator ? state.calculation : '',
|
||||
display: digit,
|
||||
error: false,
|
||||
waitingForOperand: false,
|
||||
}
|
||||
}
|
||||
if (state.display === '0') return { ...state, display: digit }
|
||||
if (state.display.replace('-', '').replace('.', '').length >= 10) return state
|
||||
@@ -54,7 +69,13 @@ export function inputDigit(
|
||||
|
||||
export function inputDecimal(state: CalculatorState): CalculatorState {
|
||||
if (state.error || state.waitingForOperand) {
|
||||
return { ...state, display: '0.', error: false, waitingForOperand: false }
|
||||
return {
|
||||
...state,
|
||||
calculation: state.pendingOperator ? state.calculation : '',
|
||||
display: '0.',
|
||||
error: false,
|
||||
waitingForOperand: false,
|
||||
}
|
||||
}
|
||||
return state.display.includes('.')
|
||||
? state
|
||||
@@ -83,6 +104,15 @@ export function chooseCalculatorOperator(
|
||||
if (state.error) return clearCalculator()
|
||||
const input = Number(state.display)
|
||||
let accumulator = state.accumulator
|
||||
let calculation = state.calculation
|
||||
|
||||
if (state.pendingOperator && state.waitingForOperand) {
|
||||
return {
|
||||
...state,
|
||||
calculation: `${calculation.slice(0, -1)}${OPERATOR_SYMBOLS[operator]}`,
|
||||
pendingOperator: operator,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
accumulator !== null &&
|
||||
@@ -93,12 +123,15 @@ export function chooseCalculatorOperator(
|
||||
if (result === null)
|
||||
return { ...clearCalculator(), display: 'Error', error: true }
|
||||
accumulator = result
|
||||
calculation = `${calculation} ${state.display} ${OPERATOR_SYMBOLS[operator]}`
|
||||
} else if (accumulator === null) {
|
||||
accumulator = input
|
||||
calculation = `${state.display} ${OPERATOR_SYMBOLS[operator]}`
|
||||
}
|
||||
|
||||
return {
|
||||
accumulator,
|
||||
calculation,
|
||||
display: formatNumber(accumulator),
|
||||
error: false,
|
||||
pendingOperator: operator,
|
||||
@@ -118,6 +151,7 @@ export function resolveCalculator(state: CalculatorState): CalculatorState {
|
||||
return { ...clearCalculator(), display: 'Error', error: true }
|
||||
return {
|
||||
accumulator: null,
|
||||
calculation: `${state.calculation} ${state.display} =`,
|
||||
display: formatNumber(result),
|
||||
error: false,
|
||||
pendingOperator: null,
|
||||
|
||||
@@ -21,8 +21,8 @@ describe('timestamp clocks', () => {
|
||||
expect(timerPickerMilliseconds('01:01:01')).toBe(3_661_000)
|
||||
})
|
||||
|
||||
it('formats stopwatch durations with actual milliseconds', () => {
|
||||
expect(formatStopwatch(61_234)).toBe('01:01.234')
|
||||
it('formats stopwatch durations with hundredths of a second', () => {
|
||||
expect(formatStopwatch(61_234)).toBe('01:01.23')
|
||||
})
|
||||
|
||||
it('calculates bounded timer progress from remaining duration', () => {
|
||||
|
||||
@@ -21,8 +21,8 @@ export function formatStopwatch(milliseconds: number): string {
|
||||
const totalMilliseconds = Math.floor(milliseconds)
|
||||
const minutes = Math.floor(totalMilliseconds / 60_000)
|
||||
const seconds = Math.floor((totalMilliseconds % 60_000) / 1000)
|
||||
const millisecondsPart = totalMilliseconds % 1000
|
||||
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(millisecondsPart).padStart(3, '0')}`
|
||||
const hundredths = Math.floor((totalMilliseconds % 1000) / 10)
|
||||
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(hundredths).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
export function formatTimer(milliseconds: number): string {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useCalculatorStore } from '@/stores/calculator'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
|
||||
const calculator = useCalculatorStore()
|
||||
const phone = usePhoneStore()
|
||||
const calculation = computed(() => {
|
||||
if (!calculator.calculation) return ''
|
||||
return calculator.waitingForOperand
|
||||
? calculator.calculation
|
||||
: `${calculator.calculation} ${calculator.display}`
|
||||
})
|
||||
const keys: Array<{ label: string; action: () => void; kind?: string }> = [
|
||||
{ label: 'AC', action: calculator.clear, kind: 'utility' },
|
||||
{ label: '+/−', action: calculator.toggleSign, kind: 'utility' },
|
||||
@@ -52,7 +60,10 @@ const keys: Array<{ label: string; action: () => void; kind?: string }> = [
|
||||
:aria-label="phone.t('Apps.calculator.name')"
|
||||
>
|
||||
<div class="calculator-display" aria-live="polite">
|
||||
{{ calculator.display }}
|
||||
<div class="calculator-result">{{ calculator.display }}</div>
|
||||
<div v-if="calculation" class="calculator-calculation">
|
||||
{{ calculation }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-pad">
|
||||
<button
|
||||
|
||||
@@ -103,15 +103,17 @@ const tabs = [
|
||||
{ id: 'timer', icon: TimerReset },
|
||||
] as const
|
||||
const tabBarColors = {
|
||||
strongHighlightBgIos: 'bg-[#2c2c2e]',
|
||||
strongHighlightBgIos: 'bg-[#e5e5ea] dark:bg-[#2c2c2e]',
|
||||
}
|
||||
const secondaryActionColors = {
|
||||
tonalBgIos: 'bg-[#2c2c2e] active:bg-[#3a3a3c]',
|
||||
tonalTextIos: 'text-white',
|
||||
tonalBgIos:
|
||||
'bg-[#e5e5ea] active:bg-[#d1d1d6] dark:bg-[#2c2c2e] dark:active:bg-[#3a3a3c]',
|
||||
tonalTextIos: 'text-black dark:text-white',
|
||||
}
|
||||
const positiveActionColors = {
|
||||
fillBgIos: 'bg-[#103a20] active:bg-[#174d2a]',
|
||||
fillTextIos: 'text-[#30d158]',
|
||||
fillBgIos:
|
||||
'bg-[#d9f7df] active:bg-[#c7efcf] dark:bg-[#103a20] dark:active:bg-[#174d2a]',
|
||||
fillTextIos: 'text-[#248a3d] dark:text-[#30d158]',
|
||||
}
|
||||
const toggleColors = {
|
||||
checkedBgIos: 'bg-[#30d158]',
|
||||
@@ -232,10 +234,10 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template v-else>
|
||||
<k-navbar
|
||||
v-if="tab === 'world' || tab === 'alarm'"
|
||||
v-if="tab !== 'stopwatch'"
|
||||
:title="phone.t(`Apps.clock.tabs.${tab}`)"
|
||||
:large="tab === 'world' || tab === 'alarm'"
|
||||
:transparent="tab === 'world' || tab === 'alarm'"
|
||||
large
|
||||
transparent
|
||||
class="clock-navbar"
|
||||
>
|
||||
<template #left>
|
||||
@@ -365,10 +367,6 @@ onBeforeUnmount(() => {
|
||||
</k-block>
|
||||
|
||||
<k-block v-else nested class="clock-tool clock-timer">
|
||||
<h1 class="clock-section-heading">
|
||||
{{ phone.t('Apps.clock.tabs.timer') }}
|
||||
</h1>
|
||||
|
||||
<div
|
||||
v-if="clock.timerStartedAt !== null"
|
||||
class="timer-ring"
|
||||
@@ -378,13 +376,13 @@ onBeforeUnmount(() => {
|
||||
:aria-valuenow="Math.round(timerProgress * 100)"
|
||||
:aria-valuetext="formatTimer(timerValue)"
|
||||
>
|
||||
<svg viewBox="0 0 170 170" aria-hidden="true">
|
||||
<circle class="timer-ring-track" cx="85" cy="85" r="72" />
|
||||
<svg viewBox="0 0 210 210" aria-hidden="true">
|
||||
<circle class="timer-ring-track" cx="105" cy="105" r="92" />
|
||||
<circle
|
||||
class="timer-ring-progress"
|
||||
cx="85"
|
||||
cy="85"
|
||||
r="72"
|
||||
cx="105"
|
||||
cy="105"
|
||||
r="92"
|
||||
pathLength="100"
|
||||
:style="{ strokeDashoffset: 100 - timerProgress * 100 }"
|
||||
/>
|
||||
@@ -407,7 +405,7 @@ onBeforeUnmount(() => {
|
||||
<k-list
|
||||
strong
|
||||
inset
|
||||
class="clock-konsta-list clock-timer-settings clock-timer-sound"
|
||||
class="clock-konsta-list clock-timer-settings clock-timer-note"
|
||||
>
|
||||
<k-list-input
|
||||
:label="phone.t('Apps.clock.timer.note')"
|
||||
@@ -418,7 +416,11 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</k-list>
|
||||
|
||||
<k-list strong inset class="clock-konsta-list clock-timer-settings">
|
||||
<k-list
|
||||
strong
|
||||
inset
|
||||
class="clock-konsta-list clock-timer-settings clock-timer-sound"
|
||||
>
|
||||
<k-list-item
|
||||
link
|
||||
:chevron="false"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sky Phone</title>
|
||||
<script type="module" crossorigin src="./assets/sky-index-u01-18EF.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/sky-index-DLFsufyp.css">
|
||||
<script type="module" crossorigin src="./assets/sky-index-BW7SWjn6.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/sky-index-CFphfAe0.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Reference in New Issue
Block a user