0.2.0 — a notebook in your pocket, ready to be hosted #3
+7
-1
@@ -2,7 +2,13 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<!-- `viewport-fit=cover` is what makes `env(safe-area-inset-*)` resolve to
|
||||||
|
anything other than 0. It opts the page into drawing under the notch and the
|
||||||
|
gesture bar, so it is only safe alongside the padding that keeps content out
|
||||||
|
of them — see style.css (sides), the sticky header (top) and the board
|
||||||
|
(bottom). The two halves ship together, deliberately: turning this on alone
|
||||||
|
is worse than leaving it off (task 2706). -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
<meta name="theme-color" content="#F5C518" />
|
<meta name="theme-color" content="#F5C518" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
|
|||||||
@@ -252,10 +252,18 @@ async function signOut() {
|
|||||||
>
|
>
|
||||||
Skip to notes
|
Skip to notes
|
||||||
</a>
|
</a>
|
||||||
|
<!-- `pt-[env(safe-area-inset-top)]`: with `viewport-fit=cover` the page draws
|
||||||
|
under the status bar / notch, and this is the one box that sits there — a
|
||||||
|
sticky header pinned to y=0. Resolves to 0 wherever there is no cutout, so
|
||||||
|
it costs nothing on a desktop or a flat-topped phone. -->
|
||||||
<header
|
<header
|
||||||
class="sticky top-0 z-20 border-b border-neutral-200 bg-neutral-50/90 backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/90"
|
class="sticky top-0 z-20 border-b border-neutral-200 bg-neutral-50/90 pt-[env(safe-area-inset-top,0px)] backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/90"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 px-4 py-3">
|
<!-- Wraps, so the search field can take a line of its own on a narrow screen.
|
||||||
|
In one row it was sharing ~360px with a menu button, the logo, the lens
|
||||||
|
name and four icons, which left every one of them truncated — the lens read
|
||||||
|
as "N…" and the search box as an empty pill. -->
|
||||||
|
<div class="flex flex-wrap items-center gap-x-3 gap-y-2 px-4 py-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="icon-btn sm:hidden"
|
class="icon-btn sm:hidden"
|
||||||
@@ -295,19 +303,24 @@ async function signOut() {
|
|||||||
<span class="truncate font-medium text-neutral-600 dark:text-neutral-300">{{ lensName }}</span>
|
<span class="truncate font-medium text-neutral-600 dark:text-neutral-300">{{ lensName }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="flex flex-1 justify-center">
|
<!-- `order-last w-full` drops this onto its own line below sm (a full-width
|
||||||
|
flex item forces the wrap); from sm it returns to the middle of the row.
|
||||||
|
ONE input either way, moved by CSS rather than duplicated — the `/`
|
||||||
|
shortcut focuses `searchInput`, and two of those would be one ref too
|
||||||
|
many. -->
|
||||||
|
<div class="order-last flex w-full justify-center sm:order-none sm:w-auto sm:flex-1">
|
||||||
<input
|
<input
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
:value="searchText"
|
:value="searchText"
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search notes…"
|
placeholder="Search notes…"
|
||||||
aria-label="Search notes"
|
aria-label="Search notes"
|
||||||
class="w-full max-w-md rounded-lg border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-900"
|
class="w-full max-w-md rounded-lg border border-neutral-300 bg-white px-3 py-2 text-base outline-none focus-visible:ring-2 focus-visible:ring-brand sm:py-1.5 sm:text-sm dark:border-neutral-700 dark:bg-neutral-900"
|
||||||
@input="onSearch(($event.target as HTMLInputElement).value)"
|
@input="onSearch(($event.target as HTMLInputElement).value)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex shrink-0 items-center gap-3">
|
<div class="ml-auto flex shrink-0 items-center gap-1 sm:ml-0 sm:gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center gap-1 rounded-lg bg-brand px-2.5 py-1.5 text-sm font-semibold text-neutral-900 hover:brightness-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand"
|
class="inline-flex items-center gap-1 rounded-lg bg-brand px-2.5 py-1.5 text-sm font-semibold text-neutral-900 hover:brightness-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand"
|
||||||
@@ -341,7 +354,7 @@ async function signOut() {
|
|||||||
<RouterLink
|
<RouterLink
|
||||||
v-if="!desktopApp"
|
v-if="!desktopApp"
|
||||||
to="/account"
|
to="/account"
|
||||||
class="icon-btn"
|
class="icon-btn hidden sm:inline-flex"
|
||||||
title="Linked devices"
|
title="Linked devices"
|
||||||
aria-label="Linked devices"
|
aria-label="Linked devices"
|
||||||
>
|
>
|
||||||
@@ -350,7 +363,7 @@ async function signOut() {
|
|||||||
<RouterLink
|
<RouterLink
|
||||||
v-if="session.user?.is_admin"
|
v-if="session.user?.is_admin"
|
||||||
to="/settings"
|
to="/settings"
|
||||||
class="icon-btn"
|
class="icon-btn hidden sm:inline-flex"
|
||||||
title="Settings"
|
title="Settings"
|
||||||
aria-label="Settings"
|
aria-label="Settings"
|
||||||
>
|
>
|
||||||
@@ -364,7 +377,7 @@ async function signOut() {
|
|||||||
<button
|
<button
|
||||||
v-if="!desktopApp"
|
v-if="!desktopApp"
|
||||||
type="button"
|
type="button"
|
||||||
class="icon-btn"
|
class="icon-btn hidden sm:inline-flex"
|
||||||
title="Sign out"
|
title="Sign out"
|
||||||
aria-label="Sign out"
|
aria-label="Sign out"
|
||||||
@click="signOut"
|
@click="signOut"
|
||||||
@@ -383,7 +396,7 @@ async function signOut() {
|
|||||||
@click="drawer = false"
|
@click="drawer = false"
|
||||||
></div>
|
></div>
|
||||||
<aside
|
<aside
|
||||||
class="fixed inset-y-0 left-0 z-40 w-64 -translate-x-full overflow-y-auto border-r border-neutral-200 bg-neutral-50 p-3 transition-transform duration-200 sm:static sm:z-auto sm:w-56 sm:translate-x-0 dark:border-neutral-800 dark:bg-neutral-950"
|
class="fixed inset-y-0 left-0 z-40 w-64 -translate-x-full overflow-y-auto border-r border-neutral-200 bg-neutral-50 p-3 pb-[calc(0.75rem+env(safe-area-inset-bottom,0px))] pt-[calc(0.75rem+env(safe-area-inset-top,0px))] transition-transform duration-200 sm:static sm:z-auto sm:w-56 sm:translate-x-0 sm:pb-3 sm:pt-3 dark:border-neutral-800 dark:bg-neutral-950"
|
||||||
:class="drawer ? 'translate-x-0' : ''"
|
:class="drawer ? 'translate-x-0' : ''"
|
||||||
>
|
>
|
||||||
<nav class="flex flex-col gap-0.5 text-sm" @click="drawer = false">
|
<nav class="flex flex-col gap-0.5 text-sm" @click="drawer = false">
|
||||||
@@ -469,6 +482,34 @@ async function signOut() {
|
|||||||
<Icon name="download" /> Export
|
<Icon name="download" /> Export
|
||||||
</a>
|
</a>
|
||||||
<ImportNotes />
|
<ImportNotes />
|
||||||
|
|
||||||
|
<!-- Account, settings and sign-out, for the screens where they are NOT in
|
||||||
|
the header. Four icons plus a search field never fit one phone-width
|
||||||
|
row, and the header is the wrong place to lose: it holds the only way
|
||||||
|
back to the board. Here they get room to be named instead of guessed
|
||||||
|
at from a glyph.
|
||||||
|
Hidden from sm up, where the header carries them again — so they are
|
||||||
|
in exactly one place at any width, never both. -->
|
||||||
|
<div
|
||||||
|
v-if="!desktopApp"
|
||||||
|
class="mt-3 flex flex-col gap-0.5 border-t border-neutral-200 pt-3 sm:hidden dark:border-neutral-800"
|
||||||
|
>
|
||||||
|
<p class="truncate px-3 pb-1 text-xs text-neutral-400">{{ session.user?.display_name }}</p>
|
||||||
|
<RouterLink to="/account" class="nav-link" :class="route.name === 'account' ? 'nav-link-active' : ''">
|
||||||
|
<Icon name="device" /> Linked devices
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
v-if="session.user?.is_admin"
|
||||||
|
to="/settings"
|
||||||
|
class="nav-link"
|
||||||
|
:class="route.name === 'settings' ? 'nav-link-active' : ''"
|
||||||
|
>
|
||||||
|
<Icon name="settings" /> Settings
|
||||||
|
</RouterLink>
|
||||||
|
<button type="button" class="nav-link w-full text-left" @click="signOut">
|
||||||
|
<Icon name="logout" /> Sign out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
@@ -186,27 +186,6 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
|||||||
]"
|
]"
|
||||||
:data-note-id="note.id"
|
:data-note-id="note.id"
|
||||||
>
|
>
|
||||||
<!-- Drag handle: reorder is gated behind this grip so a normal click or tap
|
|
||||||
never starts a drag. Board views only (reorderable). Still a pointer-only
|
|
||||||
affordance — there is no keyboard equivalent, hence tabindex="-1".
|
|
||||||
`touch-none` hands the gesture to us instead of the browser's scrolling,
|
|
||||||
and `hover-reveal` keeps the grip on screen where hovering is impossible
|
|
||||||
(it is now reachable there, which it was not under native drag-and-drop). -->
|
|
||||||
<button
|
|
||||||
v-if="canDrag()"
|
|
||||||
type="button"
|
|
||||||
tabindex="-1"
|
|
||||||
class="hover-reveal pointer-events-none absolute left-1.5 top-1.5 z-10 flex touch-none cursor-grab items-center rounded-full bg-white/85 p-1 text-neutral-500 opacity-0 shadow-sm ring-1 ring-black/5 backdrop-blur-sm transition hover:text-neutral-800 active:cursor-grabbing group-hover:pointer-events-auto group-hover:opacity-100 dark:bg-neutral-900/85 dark:text-neutral-400 dark:ring-white/10 dark:hover:text-neutral-100"
|
|
||||||
title="Drag to reorder"
|
|
||||||
aria-label="Drag to reorder"
|
|
||||||
@pointerdown="onGripDown"
|
|
||||||
@pointermove="onGripMove"
|
|
||||||
@pointerup="onGripUp"
|
|
||||||
@pointercancel="onGripCancel"
|
|
||||||
>
|
|
||||||
<Icon name="grip" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<img
|
<img
|
||||||
v-if="firstImage"
|
v-if="firstImage"
|
||||||
:src="firstImage.url"
|
:src="firstImage.url"
|
||||||
@@ -342,14 +321,36 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Toolbar overlays the card's top-right on hover/focus as a floating pill
|
<!-- The card's controls: the drag grip, then the action set.
|
||||||
(window-control style) instead of reserving a permanent row — so at rest
|
Placement is CSS's job, not the markup's — `.note-actions` in style.css puts
|
||||||
the card is content-sized with even padding, not text pinned to the top
|
them where the device can actually use them. Where a pointer hovers they lift
|
||||||
above an empty strip.
|
out of flow into the floating top-corner pills they have always been; where
|
||||||
`hover-reveal`: on a device that can't hover this is the ONLY way to pin,
|
nothing can hover they stay here, in flow, as a footer row. The alternative
|
||||||
colour or archive a note, so there it stays visible (see style.css). -->
|
(a permanently visible overlay) sat on top of the note's own title, which is
|
||||||
|
what a phone was showing.
|
||||||
|
`hover-reveal` still handles the visibility half — see style.css. -->
|
||||||
|
<div class="note-actions">
|
||||||
|
<!-- Reorder is gated behind this grip so a normal click or tap never starts a
|
||||||
|
drag. Board views only (reorderable). A pointer-only affordance — there is
|
||||||
|
no keyboard equivalent, hence tabindex="-1". `touch-none` hands the gesture
|
||||||
|
to us instead of the browser's scrolling. -->
|
||||||
|
<button
|
||||||
|
v-if="canDrag()"
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
class="note-grip hover-reveal pointer-events-none flex touch-none cursor-grab items-center rounded-full bg-white/85 p-1 text-neutral-500 opacity-0 shadow-sm ring-1 ring-black/5 backdrop-blur-sm transition hover:text-neutral-800 active:cursor-grabbing group-hover:pointer-events-auto group-hover:opacity-100 dark:bg-neutral-900/85 dark:text-neutral-400 dark:ring-white/10 dark:hover:text-neutral-100"
|
||||||
|
title="Drag to reorder"
|
||||||
|
aria-label="Drag to reorder"
|
||||||
|
@pointerdown="onGripDown"
|
||||||
|
@pointermove="onGripMove"
|
||||||
|
@pointerup="onGripUp"
|
||||||
|
@pointercancel="onGripCancel"
|
||||||
|
>
|
||||||
|
<Icon name="grip" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hover-reveal pointer-events-none absolute right-1.5 top-1.5 flex items-center gap-0.5 rounded-full bg-white/85 p-0.5 opacity-0 shadow-sm ring-1 ring-black/5 backdrop-blur-sm transition focus-within:pointer-events-auto focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 dark:bg-neutral-900/85 dark:ring-white/10"
|
class="note-actions-set hover-reveal pointer-events-none flex items-center gap-0.5 rounded-full bg-white/85 p-0.5 opacity-0 shadow-sm ring-1 ring-black/5 backdrop-blur-sm transition focus-within:pointer-events-auto focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 dark:bg-neutral-900/85 dark:ring-white/10"
|
||||||
>
|
>
|
||||||
<template v-if="note.trashed">
|
<template v-if="note.trashed">
|
||||||
<button type="button" class="icon-btn" title="Restore" aria-label="Restore" @click="notes.restore(note.id)">
|
<button type="button" class="icon-btn" title="Restore" aria-label="Restore" @click="notes.restore(note.id)">
|
||||||
@@ -371,6 +372,7 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
|||||||
class="icon-btn"
|
class="icon-btn"
|
||||||
title="Change color"
|
title="Change color"
|
||||||
aria-label="Change color"
|
aria-label="Change color"
|
||||||
|
:aria-expanded="colorOpen"
|
||||||
@click.stop="colorOpen = !colorOpen"
|
@click.stop="colorOpen = !colorOpen"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -408,11 +410,12 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
|||||||
<Icon name="trash" />
|
<Icon name="trash" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- Inside the action set rather than beside it, so it follows the set to
|
||||||
|
whichever corner or footer the device put it in. -->
|
||||||
<div
|
<div
|
||||||
v-if="colorOpen"
|
v-if="colorOpen"
|
||||||
class="absolute right-1.5 top-11 z-20 flex w-40 flex-wrap gap-1.5 rounded-lg border border-neutral-200 bg-white p-2 shadow-lg dark:border-neutral-700 dark:bg-neutral-800"
|
class="note-swatches flex w-40 flex-wrap gap-1.5 rounded-lg border border-neutral-200 bg-white p-2 shadow-lg dark:border-neutral-700 dark:bg-neutral-800"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-for="key in NOTE_COLOR_KEYS"
|
v-for="key in NOTE_COLOR_KEYS"
|
||||||
@@ -426,4 +429,6 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,6 +8,35 @@ body,
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Track the VISUAL viewport, not the layout viewport.
|
||||||
|
*
|
||||||
|
* On a phone the two diverge the moment the on-screen keyboard opens or the URL bar
|
||||||
|
* retracts: `100%` keeps the taller layout viewport, which slides the sticky header
|
||||||
|
* off screen and can park a focused field underneath the keyboard. `dvh` is the unit
|
||||||
|
* that follows the box actually being shown.
|
||||||
|
*
|
||||||
|
* Same three selectors rather than a fourth rule, so the box model is unchanged and
|
||||||
|
* only the number moves; behind @supports so a browser without `dvh` keeps the
|
||||||
|
* `100%` above instead of falling back to nothing. */
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Landscape on a notched phone puts the cutout down one SIDE of the page, and with
|
||||||
|
* `viewport-fit=cover` (index.html) the page is drawing under it. Applied to body so
|
||||||
|
* every view inherits it, rather than each container remembering; the insets are 0
|
||||||
|
* on every device that has no cutout, and 0 in portrait, so this costs nothing where
|
||||||
|
* it isn't needed. Top and bottom are NOT done here — a sticky header and a scrolling
|
||||||
|
* board need them in their own boxes, not on the page. */
|
||||||
|
body {
|
||||||
|
padding-left: env(safe-area-inset-left, 0px);
|
||||||
|
padding-right: env(safe-area-inset-right, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-neutral-50 text-neutral-900 antialiased;
|
@apply bg-neutral-50 text-neutral-900 antialiased;
|
||||||
}
|
}
|
||||||
@@ -61,6 +90,78 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A note card's own controls — the drag grip and the pin/colour/archive/trash set.
|
||||||
|
* ONE element each, in TWO placements, because "always visible" and "floating over
|
||||||
|
* the card's top corners" cannot both be true without the controls sitting on top of
|
||||||
|
* the note's own words.
|
||||||
|
*
|
||||||
|
* That is exactly what a phone got. `.hover-reveal` above made the pills permanent
|
||||||
|
* where nothing can hover (task 2697, correctly — they are the only way to pin or
|
||||||
|
* archive), but they were still absolutely positioned, so they covered the title:
|
||||||
|
* "thought sync tauri app" rendered as "ught sync tauri app" with the grip parked on
|
||||||
|
* the first three characters.
|
||||||
|
*
|
||||||
|
* So the default — no hover — is a footer row IN FLOW, which cannot overlap anything
|
||||||
|
* by construction. A pointer that can hover lifts them back out into the floating
|
||||||
|
* pills they have always been, where they cost no vertical space and appear only on
|
||||||
|
* approach. `display: contents` drops the flex row itself out of the way so each
|
||||||
|
* child positions against the card.
|
||||||
|
*
|
||||||
|
* Keyed on hover rather than width, for the reason `.hover-reveal` already gives: a
|
||||||
|
* narrow window on a laptop still hovers, and a wide tablet still doesn't. */
|
||||||
|
.note-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.125rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.note-grip {
|
||||||
|
/* Pushes the action set to the far edge, so the two read as opposite ends of a
|
||||||
|
* footer rather than as a clump. */
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.note-actions-set {
|
||||||
|
position: relative; /* the colour popover anchors here in both placements */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.note-actions {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.note-grip {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.375rem;
|
||||||
|
top: 0.375rem;
|
||||||
|
z-index: 10;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.note-actions-set {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.375rem;
|
||||||
|
top: 0.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The per-card colour popover, anchored to whichever end of the card the action set
|
||||||
|
* currently occupies: it opens DOWNWARD from a floating top-corner pill, and UPWARD
|
||||||
|
* from a footer row, so in both cases it grows into the card rather than off it. */
|
||||||
|
.note-swatches {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 100%;
|
||||||
|
margin-bottom: 0.375rem;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
@media (hover: hover) {
|
||||||
|
.note-swatches {
|
||||||
|
top: 100%;
|
||||||
|
bottom: auto;
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Board motion (M7). Defined once here rather than three times in BoardView's
|
/* Board motion (M7). Defined once here rather than three times in BoardView's
|
||||||
* markup, because "how the board moves" is one idea even though the pinned, other
|
* markup, because "how the board moves" is one idea even though the pinned, other
|
||||||
* and non-board grids are three TransitionGroups.
|
* and non-board grids are three TransitionGroups.
|
||||||
|
|||||||
@@ -258,16 +258,27 @@ async function onDrop(targetId: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mx-auto w-full max-w-6xl px-4 py-6">
|
<!-- `pb-[…safe-area-inset-bottom]`: the board is the last thing on the page, so
|
||||||
|
it is what ends up underneath the gesture bar once `viewport-fit=cover` lets
|
||||||
|
the page draw there. Added to the existing py-6 rather than replacing it. -->
|
||||||
|
<div class="mx-auto w-full max-w-6xl px-4 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px))] pt-6">
|
||||||
<button
|
<button
|
||||||
v-if="isMainBoard"
|
v-if="isMainBoard"
|
||||||
type="button"
|
type="button"
|
||||||
class="mx-auto mb-4 block w-full max-w-xl rounded-xl border border-dashed border-neutral-300 px-4 py-2.5 text-center text-sm text-neutral-400 transition hover:border-neutral-400 hover:text-neutral-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:hover:border-neutral-500"
|
class="mx-auto mb-4 block w-full max-w-xl rounded-xl border border-dashed border-neutral-300 px-4 py-2.5 text-center text-sm text-neutral-400 transition hover:border-neutral-400 hover:text-neutral-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:hover:border-neutral-500"
|
||||||
@click="ui.requestCompose()"
|
@click="ui.requestCompose()"
|
||||||
>
|
>
|
||||||
|
<!-- Two ways of saying the same thing, because the keyboard half is a lie on a
|
||||||
|
phone: there is no Enter key until something is already focused, and the
|
||||||
|
box has always been tappable. `hidden sm:inline` / `sm:hidden` rather than
|
||||||
|
a JS pointer check — this is presentation, and it should be right in the
|
||||||
|
first paint rather than after one. -->
|
||||||
|
<span class="hidden sm:inline">
|
||||||
Press
|
Press
|
||||||
<kbd class="mx-0.5 rounded border border-neutral-300 px-1 text-xs dark:border-neutral-600">Enter</kbd>
|
<kbd class="mx-0.5 rounded border border-neutral-300 px-1 text-xs dark:border-neutral-600">Enter</kbd>
|
||||||
or start typing to add a note
|
or start typing to add a note
|
||||||
|
</span>
|
||||||
|
<span class="sm:hidden">Tap to add a note</span>
|
||||||
</button>
|
</button>
|
||||||
<FilterBar v-if="isMainBoard" />
|
<FilterBar v-if="isMainBoard" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user