Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/lib/components/ImageSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,37 @@
const { title, image, imageAlt = "", alignImage, children }: Props = $props();
</script>

<!--
image-section --- alternating image-and-text block.
Image gets subtle scale-on-hover. Title has a primary tick prefix.
-->
<section class="mt-16 lg:grid lg:items-center">
<div style:grid-area="1 / 1">
<div
class="isolate h-[300px] overflow-clip md:h-[400px] lg:h-[500px] lg:w-1/2 xl:h-[600px] {alignImage ===
class="group isolate h-[300px] overflow-clip md:h-[400px] lg:h-[500px] lg:w-1/2 xl:h-[600px] {alignImage ===
'left'
? 'lg:rounded-r-2xl'
: 'lg:ml-auto lg:rounded-l-2xl'}"
>
<img src={image} alt={imageAlt} class="h-full w-full object-cover" />
<img
src={image}
alt={imageAlt}
loading="lazy"
decoding="async"
class="h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.03]"
/>
</div>
</div>
<div style:grid-area="1 / 1" class="container mx-auto lg:max-w-screen-lg">
<div class="p-8 lg:w-1/2 {alignImage === 'left' ? 'lg:ml-auto' : ''}">
<h2 class="text-3xl font-bold text-zinc-900 lg:text-4xl">{title}</h2>
<div class="mt-4 space-y-4 text-zinc-600">
<div class="mb-3 flex items-center gap-2 font-mono text-[11px] tracking-widest text-primary uppercase">
<span class="inline-block h-px w-5 bg-primary"></span>
<span>topic</span>
</div>
<h2 class="text-3xl leading-tight font-bold tracking-tight text-zinc-900 lg:text-4xl">
{title}
</h2>
<div class="mt-5 space-y-4 leading-relaxed text-zinc-600">
{@render children()}
</div>
</div>
Expand Down
42 changes: 41 additions & 1 deletion src/lib/components/Markdown.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,47 @@
});
</script>

<div class="prose max-w-none prose-zinc">
<!--
Editorial prose for site content. Tuned for "developer-editorial" feel:
- h2 has a thin bottom rule; h3 carries a primary section mark
- inline code uses zinc-100 chip with mono; code blocks use zinc-900 panel
- blockquote takes a primary left bar
- links use offset underline with primary decoration on hover
-->
<div class="markdown-prose prose max-w-none prose-zinc
prose-headings:text-zinc-900 prose-headings:font-semibold prose-headings:tracking-tight
prose-h2:mt-12 prose-h2:mb-4 prose-h2:pb-2 prose-h2:text-2xl prose-h2:border-b prose-h2:border-zinc-200
prose-h3:mt-8 prose-h3:mb-3 prose-h3:text-xl
prose-p:text-zinc-700 prose-p:leading-relaxed
prose-a:text-primary prose-a:no-underline hover:prose-a:underline hover:prose-a:underline-offset-4
prose-strong:text-zinc-900
prose-blockquote:border-l-2 prose-blockquote:border-primary prose-blockquote:bg-zinc-50/60 prose-blockquote:py-1 prose-blockquote:not-italic prose-blockquote:text-zinc-600
prose-code:before:content-none prose-code:after:content-none prose-code:bg-zinc-100 prose-code:text-zinc-800 prose-code:rounded prose-code:px-1.5 prose-code:py-0.5 prose-code:text-[0.875em] prose-code:font-medium
prose-pre:bg-zinc-900 prose-pre:text-zinc-100 prose-pre:rounded-xl prose-pre:shadow-inner prose-pre:border prose-pre:border-zinc-800
prose-img:rounded-xl prose-img:border prose-img:border-zinc-200/60
prose-hr:border-zinc-200
prose-li:marker:text-primary/70">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html html}
</div>

<style>
/* H3 section mark — a subtle § that signals depth without shouting. */
.markdown-prose :global(h3::before) {
content: "§";
color: oklch(76% 0.2 153);
margin-right: 0.5rem;
font-weight: 500;
opacity: 0.7;
}

/* Code blocks: nudge inline children back to inherit so the chip-style
prose-code rule doesn't compete with the dark pre background. */
.markdown-prose :global(pre code) {
background-color: transparent;
color: inherit;
padding: 0;
font-size: 0.875em;
font-weight: 400;
}
</style>
48 changes: 37 additions & 11 deletions src/lib/components/Pagination.svelte
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
<!--
Pagination
- Visual rhythm: prev/next anchored by mono arrows; current page lifted with subtle ring + shadow
- Page number cells are square-ish for typographic rhythm; mono numerals reinforce data feel
- Touch targets ≥ 44px via min-h-11 on all interactive cells
-->
<script lang="ts">
interface Props {
currentPage: number;
Expand All @@ -9,53 +15,73 @@
</script>

{#if totalPages > 1}
<div class="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-4">
<nav
aria-label="ページネーション"
class="mt-12 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-2"
>
{#if currentPage > 1}
<a
href={pageUrl(currentPage - 1)}
class="w-full rounded-lg border border-zinc-200 bg-white px-4 py-2 text-center text-sm font-medium transition-colors hover:bg-primary/5 hover:border-primary/30 hover:text-primary focus:ring-2 focus:ring-primary/50 focus:ring-offset-2 sm:w-auto"
class="group inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-4 py-2 text-sm font-medium transition-all hover:bg-primary/5 hover:border-primary/30 hover:text-primary focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 sm:w-auto"
rel="prev"
>
<span aria-hidden="true" class="font-[JetBrains_Mono,monospace] transition-transform group-hover:-translate-x-0.5">←</span>
前へ
</a>
{:else}
<span
class="w-full cursor-not-allowed rounded-lg border border-zinc-200 bg-white px-4 py-2 text-center text-sm font-medium opacity-50 sm:w-auto"
class="inline-flex min-h-11 w-full cursor-not-allowed items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-4 py-2 text-sm font-medium opacity-40 sm:w-auto"
aria-hidden="true"
>
<span class="font-[JetBrains_Mono,monospace]">←</span>
前へ
</span>
{/if}

<div class="flex flex-1 flex-wrap items-center justify-center gap-1 sm:flex-initial">
<div class="flex flex-1 flex-wrap items-center justify-center gap-1 sm:mx-2 sm:flex-initial">
{#each Array.from({ length: totalPages }, (_, i) => i + 1) as pageNum (pageNum)}
{#if totalPages <= 7 || pageNum === 1 || pageNum === totalPages || Math.abs(pageNum - currentPage) <= 1}
<a
href={pageUrl(pageNum)}
class="min-w-[2.5rem] rounded-lg border px-3 py-2 text-center text-sm font-medium transition-colors focus:ring-2 focus:ring-primary/50 focus:ring-offset-2 {currentPage ===
aria-current={currentPage === pageNum ? "page" : undefined}
aria-label="ページ {pageNum}"
class="flex min-h-11 min-w-11 items-center justify-center rounded-lg border px-3 py-2 text-center font-[JetBrains_Mono,monospace] text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 {currentPage ===
pageNum
? 'border-primary bg-primary text-white'
: 'border-zinc-200 bg-white hover:bg-primary/5 hover:border-primary/30 hover:text-primary'}"
? 'border-primary bg-primary text-white shadow-md shadow-primary/20'
: 'border-zinc-200 bg-white text-zinc-700 hover:bg-primary/5 hover:border-primary/30 hover:text-primary hover:-translate-y-px'}"
>
{pageNum}
</a>
{:else if pageNum === currentPage - 2 || pageNum === currentPage + 2}
<span class="px-1 text-zinc-500">...</span>
<span
class="flex min-h-11 w-6 items-center justify-center font-[JetBrains_Mono,monospace] text-zinc-400"
aria-hidden="true">···</span
>
{/if}
{/each}
</div>

{#if currentPage < totalPages}
<a
href={pageUrl(currentPage + 1)}
class="w-full rounded-lg border border-zinc-200 bg-white px-4 py-2 text-center text-sm font-medium transition-colors hover:bg-primary/5 hover:border-primary/30 hover:text-primary focus:ring-2 focus:ring-primary/50 focus:ring-offset-2 sm:w-auto"
class="group inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-4 py-2 text-sm font-medium transition-all hover:bg-primary/5 hover:border-primary/30 hover:text-primary focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 sm:w-auto"
rel="next"
>
次へ
<span aria-hidden="true" class="font-[JetBrains_Mono,monospace] transition-transform group-hover:translate-x-0.5">→</span>
</a>
{:else}
<span
class="w-full cursor-not-allowed rounded-lg border border-zinc-200 bg-white px-4 py-2 text-center text-sm font-medium opacity-50 sm:w-auto"
class="inline-flex min-h-11 w-full cursor-not-allowed items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-4 py-2 text-sm font-medium opacity-40 sm:w-auto"
aria-hidden="true"
>
次へ
<span class="font-[JetBrains_Mono,monospace]">→</span>
</span>
{/if}
</div>
</nav>

<p class="mt-3 text-center font-[JetBrains_Mono,monospace] text-[11px] text-zinc-400">
page {currentPage} / {totalPages}
</p>
{/if}
2 changes: 1 addition & 1 deletion src/lib/components/admin-dashboard/NeedsAttention.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
{#each draftArticles as draft (draft.id)}
<a
href="/admin/articles/edit/{draft.id}"
class="group flex items-center gap-3 rounded-xl border border-base-300/50 bg-base-200/50 p-3 transition-all hover:bg-primary/5 hover:border-primary/30"
class="group flex items-center gap-3 rounded-xl border border-base-300/50 bg-base-200/50 p-3 transition-all hover:bg-primary/5 hover:border-primary/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
>
<div class="flex h-9 w-9 items-center justify-center rounded-lg bg-warning/10">
<SquarePen class="h-4 w-4 text-warning" />
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/admin-dashboard/QuickActions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@
<div class="grid grid-cols-1 gap-3 sm:grid-cols-3">
<a
href="/admin/members/new"
class="btn btn-sm gap-2 border-base-300 bg-base-100 font-medium transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary sm:btn-md"
class="btn btn-sm gap-2 border-base-300 bg-base-100 font-medium transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 sm:btn-md"
>
<UserPlus class="h-4 w-4" />
Add Member
</a>
<a
href="/admin/articles/new"
class="btn btn-sm gap-2 border-base-300 bg-base-100 font-medium transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary sm:btn-md"
class="btn btn-sm gap-2 border-base-300 bg-base-100 font-medium transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 sm:btn-md"
>
<Pencil class="h-4 w-4" />
New Article
</a>
<a
href="/admin/projects/new"
class="btn btn-sm gap-2 border-base-300 bg-base-100 font-medium transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary sm:btn-md"
class="btn btn-sm gap-2 border-base-300 bg-base-100 font-medium transition-all hover:border-primary/30 hover:bg-primary/5 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 sm:btn-md"
>
<Folder class="h-4 w-4" />
New Project
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/admin-dashboard/RecentActivity.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
{#each recentArticles as article (article.id)}
<a
href="/admin/articles/edit/{article.id}"
class="group flex items-center gap-3 rounded-xl border border-base-300/50 bg-base-200/50 p-3 transition-all hover:bg-primary/5 hover:border-primary/30"
class="group flex items-center gap-3 rounded-xl border border-base-300/50 bg-base-200/50 p-3 transition-all hover:bg-primary/5 hover:border-primary/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
>
<div
class="flex h-9 w-9 items-center justify-center rounded-lg {article.published
Expand Down
54 changes: 32 additions & 22 deletions src/lib/components/article-form/ArticleEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -196,30 +196,40 @@
</p>

<div class="flex gap-2">
<input
type="date"
value={formatDateForSlug(slugDate)}
oninput={handleDateChange}
class="w-32 shrink-0 rounded-lg border bg-white px-2 py-1.5 font-mono text-sm text-zinc-900 focus:ring-0 focus:outline-none"
class:border-zinc-200={!displayError && !isSlugValid}
class:border-emerald-500={isSlugValid && slug.length > 0}
class:border-red-300={displayError}
/>
<input
type="text"
value={slugTitle}
oninput={handleSlugTitleChange}
class="min-w-0 flex-1 rounded-lg border bg-white px-2 py-1.5 font-mono text-sm text-zinc-900 focus:ring-0 focus:outline-none"
class:border-zinc-200={!displayError && !isSlugValid}
class:border-emerald-500={isSlugValid && slug.length > 0}
class:border-red-300={displayError}
placeholder="title-slug"
/>
<div class="flex w-32 shrink-0 flex-col gap-1">
<label for="slug-date" class="text-xs font-medium text-zinc-500">公開日</label>
<input
type="date"
id="slug-date"
value={formatDateForSlug(slugDate)}
oninput={handleDateChange}
class="w-full rounded-lg border bg-white px-2 py-1.5 font-mono text-sm text-zinc-900 focus:ring-0 focus:outline-none"
class:border-zinc-200={!displayError && !isSlugValid}
class:border-emerald-500={isSlugValid && slug.length > 0}
class:border-red-300={displayError}
/>
</div>
<div class="flex min-w-0 flex-1 flex-col gap-1">
<label for="slug-title" class="text-xs font-medium text-zinc-500">スラッグタイトル</label>
<input
type="text"
id="slug-title"
value={slugTitle}
oninput={handleSlugTitleChange}
class="w-full rounded-lg border bg-white px-2 py-1.5 font-mono text-sm text-zinc-900 focus:ring-0 focus:outline-none"
class:border-zinc-200={!displayError && !isSlugValid}
class:border-emerald-500={isSlugValid && slug.length > 0}
class:border-red-300={displayError}
placeholder="title-slug"
/>
</div>
</div>

{#if displayError}
<p class="text-xs text-red-500">{displayError}</p>
{/if}
<div aria-live="polite">
{#if displayError}
<p class="text-xs text-red-500">{displayError}</p>
{/if}
</div>

<!-- Redirect checkbox (only show when editing and slug has changed) -->
{#if initialSlug && slug !== initialSlug}
Expand Down
12 changes: 6 additions & 6 deletions src/lib/components/article-form/ArticleFormHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
successTimeout = setTimeout(() => {
saveSuccess = false;
successTimeout = null;
}, 2000);
}, 3500);
}

return () => {
Expand Down Expand Up @@ -93,9 +93,9 @@
</script>

<header
class="sticky top-0 z-20 flex flex-wrap items-center justify-between gap-2 border-b border-zinc-200 bg-white px-3 py-2 sm:px-4 sm:py-3"
class="sticky top-0 z-20 flex flex-col gap-3 border-b border-zinc-200 bg-white px-3 py-2 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between sm:gap-4 sm:px-4 sm:py-3"
>
<div class="flex items-center gap-2 sm:gap-4">
<div class="flex flex-wrap items-center gap-2 sm:gap-4">
<button
type="button"
onclick={() => goto("/admin/articles")}
Expand Down Expand Up @@ -142,7 +142,7 @@
>
<UserCircle2 class="h-3.5 w-3.5 text-zinc-400" />
{#if selectedAuthor}
<span class="max-w-24 truncate sm:max-w-32">{selectedAuthor.name}</span>
<span class="max-w-24 truncate sm:max-w-32" title={selectedAuthor.name}>{selectedAuthor.name}</span>
{:else}
<span class="text-zinc-400">No author</span>
{/if}
Expand Down Expand Up @@ -198,7 +198,7 @@
</Combobox.Root>
</div>

<div class="flex items-center gap-1 sm:gap-2">
<div class="flex w-full items-center gap-1 sm:w-auto sm:gap-2">
<!-- Preview Button -->
{#if articleId}
<button
Expand Down Expand Up @@ -241,7 +241,7 @@
<button
type="submit"
disabled={isSubmitting || saveSuccess}
class="inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-semibold text-white transition-all active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:gap-2 sm:px-4 sm:py-2 {published
class="inline-flex w-full items-center justify-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-semibold text-white transition-all active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto sm:gap-2 sm:px-4 sm:py-2 {published
? 'bg-emerald-600 hover:bg-emerald-700'
: 'bg-zinc-900 hover:bg-zinc-800'}"
>
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/confirm-modal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
onkeydown={handleModalKeydown}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-labelledby="confirm-modal-title"
tabindex="-1"
>
<!-- Icon -->
Expand All @@ -130,7 +130,7 @@
</div>

<!-- Title -->
<h3 id="modal-title" class="mt-4 text-center text-base font-semibold text-zinc-900">
<h3 id="confirm-modal-title" class="mt-4 text-center text-base font-semibold text-zinc-900">
{opts.title}
</h3>

Expand Down
Loading