Apply supplied SVG branding and polish device settings

This commit is contained in:
2026-10-12 19:11:14 +09:00
parent 34d48b9149
commit af004a2942
14 changed files with 211 additions and 32 deletions

View File

@@ -40,6 +40,7 @@ import { version as appVersion } from '../package.json';
import AccountPanel from './AccountPanel.vue';
import SendComposer from './SendComposer.vue';
import InstallButton from './InstallButton.vue';
import LogoMark from './LogoMark.vue';
import { createNearbySearch } from './nearbySearch';
import { useVisibleViewport } from './viewport';
useVisibleViewport();
@@ -631,7 +632,7 @@ async function spaceAction(space, action, memberId) {
<header class="site-header">
<div class="header-brand">
<button class="wordmark" @click="tab = 'devices'" aria-label="hop. 홈">
hop<span>.</span>
<LogoMark />
</button>
<span class="header-tagline">기기 사이를 가볍게.</span>
</div>
@@ -1129,7 +1130,7 @@ async function spaceAction(space, action, memberId) {
</template>
<template v-else-if="modal === 'about'">
<div class="service-info">
<strong class="service-brand">hop.</strong>
<LogoMark class="service-brand" label="hop." />
<p class="service-tagline">기기 사이를 가볍게.</p>
<div class="service-intro">
<p>Android, iOS, macOS, Windows.</p>
@@ -1171,17 +1172,28 @@ async function spaceAction(space, action, memberId) {
aria-label="기기 이름"
maxlength="40"
required /></label
><button class="button primary" :disabled="busy" aria-label="이름 저장">
이름 저장
</button>
><button class="button primary" :disabled="busy" aria-label="저장">저장</button>
</form>
<label class="settings-row discovery-setting"
><span>다른 기기에서 발견 허용</span
><input
type="checkbox"
:checked="device?.discoverable"
:disabled="busy"
@change="act(() => patchDevice({ discoverable: $event.target.checked }))" /></label
<label class="settings-row discovery-setting">
<span>다른 기기에서 발견 허용</span>
<span class="relative inline-flex h-6 w-11 shrink-0 items-center">
<input
class="peer absolute inset-0 m-0 h-full w-full cursor-pointer opacity-0"
type="checkbox"
role="switch"
:checked="device?.discoverable"
:disabled="busy"
@change="act(() => patchDevice({ discoverable: $event.target.checked }))"
/>
<span
aria-hidden="true"
class="pointer-events-none absolute inset-0 rounded-full bg-[var(--line)] transition-colors motion-reduce:transition-none peer-checked:bg-[var(--ink)] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-4 peer-focus-visible:outline-[var(--ink)] peer-disabled:opacity-50"
></span>
<span
aria-hidden="true"
class="pointer-events-none absolute left-0.5 top-0.5 size-5 rounded-full bg-[var(--surface)] shadow-sm transition-transform motion-reduce:transition-none peer-checked:translate-x-5 peer-disabled:opacity-50"
></span>
</span> </label
></template>
</section>
</div>

20
apps/web/src/LogoMark.vue Normal file
View File

@@ -0,0 +1,20 @@
<script setup>
import { useId } from 'vue';
import source from '../public/logo.svg?raw';
defineProps({ label: String });
const instance = useId();
// A fixed repository asset, not user/API HTML. Keep each SVG's clip IDs unique.
const markup = source
.replace(/fill="(?:white|#fff|#ffffff)"/gi, 'fill="currentColor"')
.replace(/\bid="([^"]+)"/g, (_, id) => `id="${id}-${instance}"`)
.replace(/url\(#([^)]+)\)/g, (_, id) => `url(#${id}-${instance})`);
</script>
<template>
<span
class="brand-logo"
:role="label ? 'img' : undefined"
:aria-label="label"
:aria-hidden="label ? undefined : 'true'"
v-html="markup"
></span>
</template>

View File

@@ -116,17 +116,14 @@ button:active:not(:disabled) {
border-bottom: 1px solid var(--line);
}
.wordmark {
font-size: 43px;
font-weight: 750;
letter-spacing: -3.5px;
display: block;
width: 64px;
height: 32px;
border: 0;
background: none;
padding: 0;
line-height: 1;
}
.wordmark span {
color: var(--brand);
}
.desktop-nav {
display: flex;
gap: 34px;
@@ -1257,9 +1254,6 @@ h1 span {
height: 80px;
gap: 0;
}
.wordmark {
font-size: 37px;
}
.desktop-nav {
display: none;
}
@@ -2893,9 +2887,6 @@ a.button {
.site-header {
gap: 18px;
}
.site-header .wordmark {
font-size: 32px;
}
.header-brand {
display: flex;
align-items: flex-end;
@@ -3035,7 +3026,8 @@ a.button {
gap: 10px;
}
.site-header .wordmark {
font-size: 28px;
width: 56px;
height: 28px;
}
.device-footer {
align-items: flex-start;
@@ -3447,8 +3439,9 @@ a.button {
font-size: 13px;
}
.service-brand {
font-size: 30px;
letter-spacing: -0.07em;
display: block;
width: 64px;
height: 32px;
}
.service-info p {
margin: 0;
@@ -3500,3 +3493,48 @@ a.button {
color: var(--muted);
font-size: 12px;
}
.brand-logo {
display: block;
color: var(--ink);
}
.wordmark .brand-logo {
width: 100%;
height: 100%;
}
.brand-logo svg {
display: block;
width: 100%;
height: 100%;
}
.modal .device-settings-form {
height: auto;
align-items: flex-end;
}
.modal .device-settings-form input,
.modal .device-settings-form .button {
height: 44px;
min-height: 44px;
border-radius: 12px;
padding: 8px 12px;
}
.modal .device-settings-form .button {
min-width: 72px;
}
.modal .device-settings-form input:focus-visible {
outline-offset: -2px;
}
.modal .discovery-setting {
border: 0;
padding-bottom: 0;
}
.modal .discovery-setting input {
margin: 0;
width: 100%;
height: 100%;
}
@media (max-width: 639px) {
.modal .device-settings-form {
align-items: stretch;
}
}