Slave/ui polishing #13
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-screen flex-col bg-background">
|
<div class="flex min-h-screen w-full min-w-0 flex-col overflow-x-clip bg-background">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<div class="flex min-h-0 flex-1 flex-col">
|
<div class="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-clip">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
<AppFooter />
|
<AppFooter />
|
||||||
|
|||||||
@@ -50,8 +50,16 @@
|
|||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
@apply w-full min-w-0 overflow-x-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground antialiased;
|
@apply min-w-0 overflow-x-hidden bg-background text-foreground antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
@apply w-full min-w-0 overflow-x-clip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +79,8 @@ body:has(.lightbox:target) {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 1.5rem;
|
padding: max(0.75rem, env(safe-area-inset-top)) max(0.75rem, env(safe-area-inset-right))
|
||||||
|
max(0.75rem, env(safe-area-inset-bottom)) max(0.75rem, env(safe-area-inset-left));
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightbox:target {
|
.lightbox:target {
|
||||||
@@ -87,8 +96,8 @@ body:has(.lightbox:target) {
|
|||||||
.lightbox__img {
|
.lightbox__img {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
max-width: min(95vw, 75rem);
|
max-width: min(100%, 75rem);
|
||||||
max-height: 90vh;
|
max-height: min(85dvh, 90vh);
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
@@ -98,8 +107,8 @@ body:has(.lightbox:target) {
|
|||||||
|
|
||||||
.lightbox__close {
|
.lightbox__close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1rem;
|
top: max(0.5rem, env(safe-area-inset-top));
|
||||||
right: 1rem;
|
right: max(0.5rem, env(safe-area-inset-right));
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="expandable-image">
|
<div class="expandable-image min-w-0">
|
||||||
<figure class="m-0">
|
<figure class="m-0">
|
||||||
<a
|
<a
|
||||||
:href="`#${lightboxId}`"
|
:href="`#${lightboxId}`"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="inline-flex w-fit rounded-md border border-border p-0.5">
|
<div class="flex w-full rounded-md border border-border p-0.5 sm:inline-flex sm:w-fit">
|
||||||
<button
|
<button
|
||||||
v-for="option in options"
|
v-for="option in options"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded px-3 py-1.5 text-xs font-medium transition-colors"
|
class="min-w-0 flex-1 rounded px-3 py-1.5 text-center text-xs font-medium transition-colors sm:flex-none"
|
||||||
:class="
|
:class="
|
||||||
modelValue === option.value
|
modelValue === option.value
|
||||||
? 'bg-muted text-foreground'
|
? 'bg-muted text-foreground'
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-x-auto rounded-lg border border-border">
|
<div
|
||||||
|
class="max-w-full overflow-x-auto rounded-lg border border-border contain-[layout_paint_style]"
|
||||||
|
>
|
||||||
<table class="w-full min-w-160 border-collapse text-left text-sm">
|
<table class="w-full min-w-160 border-collapse text-left text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="border-b border-border bg-muted/40">
|
<tr class="border-b border-border bg-muted">
|
||||||
<th class="sticky left-0 z-10 min-w-48 bg-muted/40 px-3 py-2.5 font-medium text-foreground">
|
<th class="sticky left-0 z-10 min-w-48 bg-muted px-3 py-2.5 font-medium text-foreground">
|
||||||
Feature
|
Feature
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
v-for="host in comparisonHosts"
|
v-for="host in comparisonHosts"
|
||||||
:key="host.id"
|
:key="host.id"
|
||||||
class="min-w-38 px-3 py-2.5 font-medium"
|
class="min-w-38 bg-muted px-3 py-2.5 font-medium"
|
||||||
:class="host.id === 'privateImage' ? 'bg-primary/5 text-foreground' : 'text-muted-foreground'"
|
:class="host.id === 'privateImage' ? 'bg-primary/5 text-foreground' : 'text-muted-foreground'"
|
||||||
>
|
>
|
||||||
{{ host.name }}
|
{{ host.name }}
|
||||||
@@ -19,7 +21,7 @@
|
|||||||
<tbody class="text-muted-foreground">
|
<tbody class="text-muted-foreground">
|
||||||
<tr v-for="row in comparisonRows" :key="row.feature" class="border-b border-border last:border-0">
|
<tr v-for="row in comparisonRows" :key="row.feature" class="border-b border-border last:border-0">
|
||||||
<td
|
<td
|
||||||
class="sticky left-0 z-10 bg-card px-3 py-2.5 font-medium text-foreground shadow-[1px_0_0_0_var(--border)]"
|
class="sticky left-0 z-10 bg-background px-3 py-2.5 font-medium text-foreground shadow-[1px_0_0_0_var(--border)]"
|
||||||
>
|
>
|
||||||
{{ row.feature }}
|
{{ row.feature }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<footer class="mt-auto border-t border-border bg-card">
|
<footer class="mt-auto border-t border-border bg-card">
|
||||||
<div
|
<div
|
||||||
class="mx-auto flex max-w-5xl flex-col items-center gap-2 px-4 py-5 text-center text-xs text-muted-foreground sm:px-6 sm:text-sm"
|
class="mx-auto flex max-w-5xl flex-col items-center gap-2 px-3 py-4 text-center text-xs text-muted-foreground sm:px-6 sm:py-5 sm:text-sm"
|
||||||
>
|
>
|
||||||
<p class="flex flex-wrap items-center justify-center gap-x-2 gap-y-1">
|
<p class="flex flex-wrap items-center justify-center gap-x-2 gap-y-1">
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="border-b border-border bg-card">
|
<header class="border-b border-border bg-card">
|
||||||
<div class="mx-auto flex h-14 max-w-5xl items-center justify-between gap-4 px-4 sm:px-6">
|
<div class="mx-auto flex h-14 max-w-5xl items-center justify-between gap-2 px-3 sm:gap-4 sm:px-6">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="{ name: ROUTE_NAMES.Upload }"
|
:to="{ name: ROUTE_NAMES.Upload }"
|
||||||
class="shrink-0 transition-opacity hover:opacity-80"
|
class="min-w-0 shrink transition-opacity hover:opacity-80"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt="Private Image"
|
alt="Private Image"
|
||||||
class="h-8 w-auto sm:h-9"
|
class="h-7 w-auto max-w-[min(100%,12rem)] sm:h-8 md:h-9"
|
||||||
width="1024"
|
width="1024"
|
||||||
height="228"
|
height="228"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="flex min-h-48 flex-col rounded-lg border border-border bg-card">
|
<section class="flex min-h-48 flex-col rounded-lg border border-border bg-card">
|
||||||
<header class="border-b border-border px-4 py-3">
|
<header class="border-b border-border px-3 py-2.5 sm:px-4 sm:py-3">
|
||||||
<h2 class="text-sm font-medium text-card-foreground">Metadata Inspector</h2>
|
<h2 class="text-sm font-medium text-card-foreground">Metadata Inspector</h2>
|
||||||
<p v-if="fileName" class="mt-0.5 truncate text-xs text-muted-foreground">{{ fileName }}</p>
|
<p v-if="fileName" class="mt-0.5 truncate text-xs text-muted-foreground">{{ fileName }}</p>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<pre
|
<pre
|
||||||
class="max-h-96 min-h-32 flex-1 overflow-auto rounded-md bg-muted p-3 text-xs leading-relaxed whitespace-pre-wrap text-foreground"
|
class="max-h-96 min-h-32 flex-1 overflow-auto rounded-md bg-muted p-3 text-xs leading-relaxed break-all whitespace-pre-wrap text-foreground"
|
||||||
><slot /></pre>
|
><slot /></pre>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<label class="flex flex-col gap-1.5 text-xs">
|
<label class="flex flex-col gap-1.5 text-xs">
|
||||||
<span class="font-medium text-muted-foreground">{{ label }}</span>
|
<span class="font-medium text-muted-foreground">{{ label }}</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-col gap-2 sm:flex-row">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
readonly
|
readonly
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="shrink-0 rounded-md border border-border px-3 py-2 text-xs font-medium transition-colors hover:bg-muted/60"
|
class="shrink-0 rounded-md border border-border px-3 py-2.5 text-xs font-medium transition-colors hover:bg-muted/60 sm:py-2"
|
||||||
@click="onCopyClick"
|
@click="onCopyClick"
|
||||||
>
|
>
|
||||||
{{ copied ? 'Copied' : 'Copy' }}
|
{{ copied ? 'Copied' : 'Copy' }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div v-if="open" class="fixed inset-0 z-50 flex items-end justify-center bg-black/50 p-4 sm:items-center">
|
<div v-if="open" class="fixed inset-0 z-50 flex items-end justify-center overflow-y-auto bg-black/50 p-4 sm:items-center">
|
||||||
<div class="flex w-full max-w-lg flex-col rounded-lg border border-border bg-card shadow-lg">
|
<div class="flex w-full max-w-lg flex-col rounded-lg border border-border bg-card shadow-lg">
|
||||||
<header class="border-b border-border px-4 py-3">
|
<header class="border-b border-border px-4 py-3">
|
||||||
<h2 class="text-base font-medium text-card-foreground">
|
<h2 class="text-base font-medium text-card-foreground">
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="open && metadataErrorConfirmOpen"
|
v-if="open && metadataErrorConfirmOpen"
|
||||||
class="fixed inset-0 z-60 flex items-end justify-center bg-black/50 p-4 sm:items-center"
|
class="fixed inset-0 z-60 flex items-end justify-center overflow-y-auto bg-black/50 p-4 sm:items-center"
|
||||||
>
|
>
|
||||||
<div class="flex w-full max-w-md flex-col rounded-lg border border-border bg-card shadow-lg">
|
<div class="flex w-full max-w-md flex-col rounded-lg border border-border bg-card shadow-lg">
|
||||||
<header class="border-b border-border px-4 py-3">
|
<header class="border-b border-border px-4 py-3">
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex w-full flex-col gap-3">
|
<div class="flex w-full flex-col gap-3">
|
||||||
<p class="mb-3 text-base text-muted-foreground">
|
<p class="text-sm text-muted-foreground sm:text-base">
|
||||||
Drop anywhere, paste or choose files. Max {{ maxQueueSize }} images, up to {{ maxImageSizeLabel }} each.
|
Drop anywhere, paste or choose files. Max {{ maxQueueSize }} images, up to {{ maxImageSizeLabel }} each.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<label
|
<label
|
||||||
class="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90"
|
class="inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90 sm:w-auto"
|
||||||
:class="disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'"
|
:class="disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'"
|
||||||
>
|
>
|
||||||
Choose images
|
Choose images
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!uploadConfirmed"
|
v-if="!uploadConfirmed"
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex shrink-0 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
|
class="inline-flex w-full shrink-0 items-center justify-center rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto"
|
||||||
:disabled="!canOpenConfigure"
|
:disabled="!canOpenConfigure"
|
||||||
@click="configureModalOpen = true"
|
@click="configureModalOpen = true"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<li v-for="row in rows" :key="row.key">
|
<li v-for="row in rows" :key="row.key">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/60"
|
class="flex w-full items-start gap-3 px-3 py-3 text-left transition-colors hover:bg-muted/60 sm:items-center sm:px-4"
|
||||||
:class="isRowSelected(row) ? 'bg-muted/80' : ''"
|
:class="isRowSelected(row) ? 'bg-muted/80' : ''"
|
||||||
@click="emit('select', row.file)"
|
@click="emit('select', row.file)"
|
||||||
>
|
>
|
||||||
@@ -19,16 +19,22 @@
|
|||||||
class="h-10 w-10 shrink-0 rounded border border-border bg-muted/60"
|
class="h-10 w-10 shrink-0 rounded border border-border bg-muted/60"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium">{{ row.file.name }}</span>
|
<div class="min-w-0 flex-1 space-y-1">
|
||||||
|
<div class="flex min-w-0 items-baseline justify-between gap-2">
|
||||||
|
<span class="min-w-0 truncate text-sm font-medium">{{ row.file.name }}</span>
|
||||||
<span class="shrink-0 text-xs text-muted-foreground">
|
<span class="shrink-0 text-xs text-muted-foreground">
|
||||||
{{ prettyBytes(row.file.size, { maximumFractionDigits: 1 }) }}
|
{{ prettyBytes(row.file.size, { maximumFractionDigits: 1 }) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="shrink-0 max-w-32 truncate text-xs sm:max-w-40" :class="metadataStatusClass(row.metadata)">
|
</div>
|
||||||
|
<div class="flex min-w-0 flex-col gap-0.5 text-xs sm:flex-row sm:flex-wrap sm:gap-x-3">
|
||||||
|
<span class="min-w-0 truncate" :class="metadataStatusClass(row.metadata)">
|
||||||
{{ row.metadata.message }}
|
{{ row.metadata.message }}
|
||||||
</span>
|
</span>
|
||||||
<span class="shrink-0 max-w-32 truncate text-xs sm:max-w-40" :class="uploadStatusClass(row.upload)">
|
<span class="min-w-0 truncate" :class="uploadStatusClass(row.upload)">
|
||||||
{{ row.upload.message }}
|
{{ row.upload.message }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -52,12 +52,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<label class="flex flex-col gap-1.5 text-sm">
|
<label class="flex min-w-0 flex-col gap-1.5 text-sm">
|
||||||
<span class="font-medium">Expiry</span>
|
<span class="font-medium">Expiry</span>
|
||||||
<select
|
<select
|
||||||
v-model="settings.expiryPreset"
|
v-model="settings.expiryPreset"
|
||||||
class="rounded-md border border-input bg-background px-3 py-2 text-sm"
|
class="w-full rounded-md border border-input bg-background px-3 py-2 text-base"
|
||||||
>
|
>
|
||||||
<option v-for="option in expiryOptions" :key="option.value" :value="option.value">
|
<option v-for="option in expiryOptions" :key="option.value" :value="option.value">
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
@@ -65,11 +65,11 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="flex flex-col gap-1.5 text-sm">
|
<label class="flex min-w-0 flex-col gap-1.5 text-sm">
|
||||||
<span class="font-medium">Max views</span>
|
<span class="font-medium">Max views</span>
|
||||||
<select
|
<select
|
||||||
v-model="settings.maxViewsPreset"
|
v-model="settings.maxViewsPreset"
|
||||||
class="rounded-md border border-input bg-background px-3 py-2 text-sm"
|
class="w-full rounded-md border border-input bg-background px-3 py-2 text-base"
|
||||||
>
|
>
|
||||||
<option v-for="option in maxViewsOptions" :key="option.value" :value="option.value">
|
<option v-for="option in maxViewsOptions" :key="option.value" :value="option.value">
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
v-model="settings.password"
|
v-model="settings.password"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
class="rounded-md border border-input bg-background px-3 py-2 text-sm"
|
class="rounded-md border border-input bg-background px-3 py-2 text-base"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="flex min-h-48 flex-col rounded-lg border border-border bg-card">
|
<section class="flex min-h-48 flex-col rounded-lg border border-border bg-card">
|
||||||
<header class="border-b border-border px-4 py-3">
|
<header class="border-b border-border px-3 py-2.5 sm:px-4 sm:py-3">
|
||||||
<h2 class="text-sm font-medium text-card-foreground">Share links</h2>
|
<h2 class="text-sm font-medium text-card-foreground">Share links</h2>
|
||||||
<p v-if="fileName" class="mt-0.5 truncate text-xs text-muted-foreground">{{ fileName }}</p>
|
<p v-if="fileName" class="mt-0.5 truncate text-xs text-muted-foreground">{{ fileName }}</p>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="mx-auto max-w-5xl flex-1 px-4 py-8 sm:px-6">
|
<main class="mx-auto flex w-full min-w-0 max-w-5xl flex-1 flex-col overflow-x-hidden px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<article>
|
<article>
|
||||||
<h1 class="text-2xl font-semibold tracking-tight sm:text-3xl">About Private Image</h1>
|
<h1 class="text-2xl font-semibold tracking-tight sm:text-3xl">About Private Image</h1>
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<section id="exiftool" class="mt-14 scroll-mt-20">
|
<section id="exiftool" class="mt-14 scroll-mt-16 sm:scroll-mt-20">
|
||||||
<h2 class="text-lg font-medium text-foreground">ExifTool runs in your browser</h2>
|
<h2 class="text-lg font-medium text-foreground">ExifTool runs in your browser</h2>
|
||||||
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
Private Image uses ExifTool, a widely used metadata tool normally run on a desktop, compiled to work in
|
Private Image uses ExifTool, a widely used metadata tool normally run on a desktop, compiled to work in
|
||||||
@@ -45,10 +45,10 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="share-modes" class="mt-14 scroll-mt-20">
|
<section id="share-modes" class="mt-14 scroll-mt-16 sm:scroll-mt-20">
|
||||||
<h2 class="text-lg font-medium text-foreground">Share modes</h2>
|
<h2 class="text-lg font-medium text-foreground">Share modes</h2>
|
||||||
|
|
||||||
<h3 id="zero-knowledge" class="mt-6 scroll-mt-20 text-base font-medium text-foreground">
|
<h3 id="zero-knowledge" class="mt-6 scroll-mt-16 sm:scroll-mt-20 text-base font-medium text-foreground">
|
||||||
Zero knowledge
|
Zero knowledge
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-2 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
expand-label="View larger zero knowledge flow diagram"
|
expand-label="View larger zero knowledge flow diagram"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3 id="hotlink" class="mt-8 scroll-mt-20 text-base font-medium text-foreground">Hotlink</h3>
|
<h3 id="hotlink" class="mt-8 scroll-mt-16 sm:scroll-mt-20 text-base font-medium text-foreground">Hotlink</h3>
|
||||||
<p class="mt-2 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-2 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
Hotlink mode keeps images encrypted at rest but decrypts them on the server when they're requested. This
|
Hotlink mode keeps images encrypted at rest but decrypts them on the server when they're requested. This
|
||||||
makes it possible to embed images directly in forum posts, chats, wikis, and other places that support
|
makes it possible to embed images directly in forum posts, chats, wikis, and other places that support
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="crypto-shred" class="mt-14 scroll-mt-20">
|
<section id="crypto-shred" class="mt-14 scroll-mt-16 sm:scroll-mt-20">
|
||||||
<h2 class="text-lg font-medium text-foreground">Crypto-shredding</h2>
|
<h2 class="text-lg font-medium text-foreground">Crypto-shredding</h2>
|
||||||
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
Deleting a file doesn't always mean every copy of its data has disappeared from a storage device.
|
Deleting a file doesn't always mean every copy of its data has disappeared from a storage device.
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="limits" class="mt-14 scroll-mt-20">
|
<section id="limits" class="mt-14 scroll-mt-16 sm:scroll-mt-20">
|
||||||
<h2 class="text-lg font-medium text-foreground">Set your own limits</h2>
|
<h2 class="text-lg font-medium text-foreground">Set your own limits</h2>
|
||||||
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
Choose how long each upload stays available, from a short, fixed period to no expiry. You can also limit
|
Choose how long each upload stays available, from a short, fixed period to no expiry. You can also limit
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="self-hosting" class="mt-14 scroll-mt-20">
|
<section id="self-hosting" class="mt-14 scroll-mt-16 sm:scroll-mt-20">
|
||||||
<h2 class="text-lg font-medium text-foreground">Self-hosting</h2>
|
<h2 class="text-lg font-medium text-foreground">Self-hosting</h2>
|
||||||
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
Private Image is open source, so you can run your own instance on hardware you control instead of
|
Private Image is open source, so you can run your own instance on hardware you control instead of
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="clearnet-and-onion" class="mt-14 scroll-mt-20">
|
<section id="clearnet-and-onion" class="mt-14 scroll-mt-16 sm:scroll-mt-20">
|
||||||
<h2 class="text-lg font-medium text-foreground">Clearnet and onion access</h2>
|
<h2 class="text-lg font-medium text-foreground">Clearnet and onion access</h2>
|
||||||
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
Private Image is available over the regular web (clearnet, via HTTPS) and through Tor as an onion
|
Private Image is available over the regular web (clearnet, via HTTPS) and through Tor as an onion
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="compare" class="mt-16 scroll-mt-20">
|
<section id="compare" class="mt-16 min-w-0 scroll-mt-16 sm:scroll-mt-20">
|
||||||
<h2 class="text-lg font-medium text-foreground">How other image hosts compare</h2>
|
<h2 class="text-lg font-medium text-foreground">How other image hosts compare</h2>
|
||||||
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
<p class="mt-3 text-sm leading-relaxed text-muted-foreground sm:text-base sm:leading-relaxed">
|
||||||
The table below compares Private Image with ImgBB, Imgur, and Catbox, three free image hosts commonly
|
The table below compares Private Image with ImgBB, Imgur, and Catbox, three free image hosts commonly
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="mx-auto flex max-w-lg flex-1 flex-col items-center justify-center gap-3 p-8 text-center">
|
<main class="mx-auto flex w-full max-w-lg flex-1 flex-col items-center justify-center gap-3 px-4 py-6 text-center sm:p-8">
|
||||||
<p v-if="state === 'invalid'" class="text-sm text-muted-foreground">
|
<p v-if="state === 'invalid'" class="text-sm text-muted-foreground">
|
||||||
This delete link is incomplete. Use the delete URL from your upload results.
|
This delete link is incomplete. Use the delete URL from your upload results.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="flex flex-1 items-center justify-center p-4">
|
<main class="flex w-full min-w-0 flex-1 flex-col items-center justify-center px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<img v-if="imageSrc" :src="imageSrc" class="max-h-[80vh] max-w-full object-contain" />
|
<img
|
||||||
|
v-if="imageSrc"
|
||||||
|
:src="imageSrc"
|
||||||
|
class="max-h-[min(80vh,100dvh)] w-full object-contain"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6 p-8">
|
<main class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-4 p-4 sm:gap-6 sm:p-6 lg:p-8">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<UploadIntakeControls
|
<UploadIntakeControls
|
||||||
ref="intakeControlsRef"
|
ref="intakeControlsRef"
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="flex flex-1 items-center justify-center p-4">
|
<main class="flex w-full min-w-0 flex-1 flex-col items-center justify-center px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<p v-if="fetching" class="text-sm text-muted-foreground">Loading image...</p>
|
<p v-if="fetching" class="text-sm text-muted-foreground sm:text-base">Loading image...</p>
|
||||||
|
|
||||||
<form v-else-if="zkPayload" class="flex w-full max-w-sm flex-col gap-3" @submit.prevent="onUnlockSubmit">
|
<form
|
||||||
|
v-else-if="zkPayload"
|
||||||
|
class="flex w-full max-w-sm flex-col gap-3"
|
||||||
|
@submit.prevent="onUnlockSubmit"
|
||||||
|
>
|
||||||
<label class="flex flex-col gap-1.5 text-sm">
|
<label class="flex flex-col gap-1.5 text-sm">
|
||||||
<span>Password</span>
|
<span>Password</span>
|
||||||
<input
|
<input
|
||||||
v-model="password"
|
v-model="password"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
class="rounded-md border border-input bg-background px-3 py-2 text-sm"
|
class="rounded-md border border-input bg-background px-3 py-2.5 text-sm sm:py-2"
|
||||||
:disabled="unlocking"
|
:disabled="unlocking"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -18,14 +22,18 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
|
class="inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60 sm:py-2"
|
||||||
:disabled="unlocking || !password"
|
:disabled="unlocking || !password"
|
||||||
>
|
>
|
||||||
{{ unlocking ? 'Unlocking...' : 'View image' }}
|
{{ unlocking ? 'Unlocking...' : 'View image' }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<img v-else-if="imageObjectUrl" :src="imageObjectUrl" class="max-h-[80vh] max-w-full object-contain" />
|
<img
|
||||||
|
v-else-if="imageObjectUrl"
|
||||||
|
:src="imageObjectUrl"
|
||||||
|
class="max-h-[min(80vh,100dvh)] w-full object-contain"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user