generated from tpl/obsidian-sample-plugin
Rewrite bookshelf view from scratch
This commit is contained in:
parent
2df1cf4b30
commit
349fcd903e
|
@ -111,6 +111,7 @@ const context = await esbuild.context({
|
|||
"@lezer/lr",
|
||||
...builtins,
|
||||
],
|
||||
conditions: ["svelte"],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
|
|
|
@ -44,9 +44,13 @@
|
|||
"typescript": "5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@humanspeak/svelte-virtual-list": "^0.2.6",
|
||||
"@leveluptuts/svelte-fit": "^1.0.3",
|
||||
"chart.js": "^4.5.0",
|
||||
"chroma-js": "^3.1.2",
|
||||
"esbuild-sass-plugin": "^3.3.1",
|
||||
"fitty": "^2.4.2",
|
||||
"just-memoize": "^2.2.0",
|
||||
"textfit": "^2.4.0",
|
||||
"uuid": "^11.1.0",
|
||||
"yaml": "^2.8.0",
|
||||
|
|
|
@ -8,6 +8,12 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
'@humanspeak/svelte-virtual-list':
|
||||
specifier: ^0.2.6
|
||||
version: 0.2.6(svelte@5.34.8)
|
||||
'@leveluptuts/svelte-fit':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
chart.js:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
|
@ -17,6 +23,12 @@ importers:
|
|||
esbuild-sass-plugin:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1(esbuild@0.17.3)(sass-embedded@1.89.2)
|
||||
fitty:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
just-memoize:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
textfit:
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0
|
||||
|
@ -312,6 +324,11 @@ packages:
|
|||
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
||||
'@humanspeak/svelte-virtual-list@0.2.6':
|
||||
resolution: {integrity: sha512-nfD81b4LQw2bTSFYV/M0ky/pnUkfI0KYr5qCVJHZe6h3dUtXoNIhoxypI6JGBcpW3D8jN5Y53NOkfDggJNb5nA==}
|
||||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1':
|
||||
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
||||
engines: {node: '>=12.22'}
|
||||
|
@ -345,6 +362,9 @@ packages:
|
|||
'@kurkle/color@0.3.4':
|
||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||
|
||||
'@leveluptuts/svelte-fit@1.0.3':
|
||||
resolution: {integrity: sha512-Hg8Xz06Mf1pwI92cY60LdFKDjXUkix0KcNP5orgGjtV7ecfK4zZ8gwPHOSDioZt73/7Muj1O43QzvtK2oPwEfA==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
|
@ -885,6 +905,9 @@ packages:
|
|||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
fitty@2.4.2:
|
||||
resolution: {integrity: sha512-GNhWgImK4+wEkgEZjBkQMyu5NLSmmryg/CaRP7zYby+TWzCrUou6BHL+iqbjKzJRXMyzuJkH+LBB1+lh4oO77g==}
|
||||
|
||||
flat-cache@4.0.1:
|
||||
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||
engines: {node: '>=16'}
|
||||
|
@ -1138,6 +1161,9 @@ packages:
|
|||
json-stable-stringify-without-jsonify@1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
|
||||
just-memoize@2.2.0:
|
||||
resolution: {integrity: sha512-zriv+MY+61RXT0QsrO1ZJtL5umouqqSWmCGBkp2wJm35kniunBAA4qhUKx8Lvg/QcwrF9xuw9E6PkevKFf4boQ==}
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
|
@ -1951,6 +1977,11 @@ snapshots:
|
|||
'@humanfs/core': 0.19.1
|
||||
'@humanwhocodes/retry': 0.3.1
|
||||
|
||||
'@humanspeak/svelte-virtual-list@0.2.6(svelte@5.34.8)':
|
||||
dependencies:
|
||||
esm-env: 1.2.2
|
||||
svelte: 5.34.8
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1': {}
|
||||
|
||||
'@humanwhocodes/retry@0.3.1': {}
|
||||
|
@ -1976,6 +2007,8 @@ snapshots:
|
|||
|
||||
'@kurkle/color@0.3.4': {}
|
||||
|
||||
'@leveluptuts/svelte-fit@1.0.3': {}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
|
@ -2608,6 +2641,8 @@ snapshots:
|
|||
locate-path: 6.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
fitty@2.4.2: {}
|
||||
|
||||
flat-cache@4.0.1:
|
||||
dependencies:
|
||||
flatted: 3.3.3
|
||||
|
@ -2869,6 +2904,8 @@ snapshots:
|
|||
|
||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||
|
||||
just-memoize@2.2.0: {}
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import Book from "@ui/components/bookshelf/Book.svelte";
|
||||
import Bookshelf from "@ui/components/bookshelf/Bookshelf.svelte";
|
||||
import BookStack from "@ui/components/bookshelf/BookStack.svelte";
|
||||
import BookStackElement from "@ui/components/bookshelf/BookStackElement.svelte";
|
||||
import {
|
||||
createMetadata,
|
||||
setMetadataContext,
|
||||
|
@ -22,6 +21,8 @@
|
|||
import DateFilter from "@ui/components/DateFilter.svelte";
|
||||
import Rating from "@ui/components/Rating.svelte";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import memoize from "just-memoize";
|
||||
import VirtualList from "@humanspeak/svelte-virtual-list";
|
||||
|
||||
interface Props {
|
||||
plugin: BookTrackerPlugin;
|
||||
|
@ -32,11 +33,11 @@
|
|||
id: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
author: string;
|
||||
authors: string[];
|
||||
width: number;
|
||||
color: ColorName;
|
||||
design: (typeof designs)[number];
|
||||
orientation: undefined | "tilted" | "on-display";
|
||||
orientation: "default" | "tilted" | "front";
|
||||
file: TFile;
|
||||
}
|
||||
|
||||
|
@ -55,12 +56,7 @@
|
|||
const metadataStore = createMetadata(plugin, settings.statusFilter, true);
|
||||
setMetadataContext(metadataStore);
|
||||
|
||||
const designs = [
|
||||
"default",
|
||||
"colored-spine",
|
||||
"dual-top-bands",
|
||||
"split-bands",
|
||||
] as const;
|
||||
const designs = ["default", "dual-top-bands", "split-bands"] as const;
|
||||
|
||||
const randomDesign = () => randomElement(designs);
|
||||
const randomColor = () => randomElement(COLOR_NAMES);
|
||||
|
@ -68,11 +64,11 @@
|
|||
const n = randomFloat();
|
||||
|
||||
if (n < 0.55) {
|
||||
return undefined;
|
||||
return "default";
|
||||
} else if (n < 0.8) {
|
||||
return "tilted";
|
||||
} else {
|
||||
return "on-display";
|
||||
return "front";
|
||||
}
|
||||
}
|
||||
const randomStackChance = () => randomFloat() > 0.9;
|
||||
|
@ -91,23 +87,34 @@
|
|||
}
|
||||
}
|
||||
|
||||
function getBookData(metadata: FileMetadata): BookData {
|
||||
return {
|
||||
id: metadata.file.path,
|
||||
title: metadata.frontmatter[settings.titleProperty],
|
||||
subtitle: settings.subtitleProperty
|
||||
? metadata.frontmatter[settings.subtitleProperty]
|
||||
: undefined,
|
||||
author: metadata.frontmatter[settings.authorsProperty].join(", "),
|
||||
width: metadata.frontmatter[
|
||||
settingsStore.settings.pageCountProperty
|
||||
],
|
||||
color: randomColor(),
|
||||
design: randomDesign(),
|
||||
orientation: randomOrientation(),
|
||||
file: metadata.file,
|
||||
};
|
||||
}
|
||||
const getBookData = memoize(
|
||||
(metadata: FileMetadata): BookData => {
|
||||
const orientation = randomOrientation();
|
||||
|
||||
return {
|
||||
id: metadata.file.path,
|
||||
title: metadata.frontmatter[settings.titleProperty],
|
||||
subtitle: settings.subtitleProperty
|
||||
? metadata.frontmatter[settings.subtitleProperty]
|
||||
: undefined,
|
||||
authors: metadata.frontmatter[settings.authorsProperty],
|
||||
width: Math.min(
|
||||
Math.max(
|
||||
20,
|
||||
metadata.frontmatter[
|
||||
settingsStore.settings.pageCountProperty
|
||||
] / 10,
|
||||
),
|
||||
100,
|
||||
),
|
||||
color: randomColor(),
|
||||
design: orientation === "front" ? "default" : randomDesign(),
|
||||
orientation: randomOrientation(),
|
||||
file: metadata.file,
|
||||
};
|
||||
},
|
||||
(metadata: FileMetadata) => metadata.file.path,
|
||||
);
|
||||
|
||||
let view = $state(settings.defaultView);
|
||||
let books: (BookData | BookStackData)[] = $state([]);
|
||||
|
@ -156,13 +163,14 @@
|
|||
<Bookshelf>
|
||||
{#each books as book (book.id)}
|
||||
{#if "books" in book}
|
||||
<BookStack totalChildren={book.books.length}>
|
||||
<BookStack>
|
||||
{#each book.books as bookData (bookData.id)}
|
||||
<BookStackElement
|
||||
<Book
|
||||
title={bookData.title}
|
||||
subtitle={bookData.subtitle}
|
||||
color={bookData.color}
|
||||
design={bookData.design}
|
||||
orientation="flat"
|
||||
onClick={() =>
|
||||
plugin.app.workspace
|
||||
.getLeaf("tab")
|
||||
|
@ -174,7 +182,7 @@
|
|||
<Book
|
||||
title={book.title}
|
||||
subtitle={book.subtitle}
|
||||
author={book.author}
|
||||
authors={book.authors}
|
||||
width={book.width}
|
||||
color={book.color}
|
||||
design={book.design}
|
||||
|
@ -275,7 +283,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.shelf-code-block {
|
||||
.controls {
|
||||
margin-bottom: 1rem;
|
||||
|
|
|
@ -1,205 +1,327 @@
|
|||
<script lang="ts">
|
||||
import Self from "./Book.svelte";
|
||||
import { Color, isColorName, type ColorName } from "@utils/color";
|
||||
import BookTilted from "./BookTilted.svelte";
|
||||
import BookOnDisplay from "./BookOnDisplay.svelte";
|
||||
import BookText from "./BookText.svelte";
|
||||
const BOOK_SIZE_DEFAULT: number = 40;
|
||||
const BOOK_SIZE_MIN: number = 15;
|
||||
import type { Snippet } from "svelte";
|
||||
import BookshelfItem from "./BookshelfItem.svelte";
|
||||
import { Color, type ColorName } from "@utils/color";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { fit } from "@leveluptuts/svelte-fit";
|
||||
|
||||
interface BookProps {
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
author?: string;
|
||||
color?: ColorName | string;
|
||||
design?: "default" | "colored-spine" | "dual-top-bands" | "split-bands";
|
||||
orientation?: "tilted" | "on-display";
|
||||
authors?: string[];
|
||||
height?: number;
|
||||
width?: number;
|
||||
color?: ColorName;
|
||||
orientation?: "default" | "tilted" | "flat" | "front";
|
||||
design?: "default" | "split-bands" | "dual-top-bands";
|
||||
role?: ARIAMixin["role"];
|
||||
tabindex?: HTMLAttributes<HTMLDivElement>["tabindex"];
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
title,
|
||||
subtitle,
|
||||
author,
|
||||
color: colorRaw = "green",
|
||||
authors,
|
||||
height = 200,
|
||||
width = 40,
|
||||
color: colorName = "blue",
|
||||
orientation = "default",
|
||||
design = "default",
|
||||
orientation,
|
||||
height,
|
||||
width,
|
||||
role = "link",
|
||||
tabindex = 0,
|
||||
onClick,
|
||||
}: BookProps = $props();
|
||||
}: Props = $props();
|
||||
|
||||
function normalizeWidth(input: number | undefined) {
|
||||
if (input) {
|
||||
if (input <= 150) {
|
||||
return BOOK_SIZE_MIN;
|
||||
}
|
||||
return input / 10;
|
||||
$effect(() => {
|
||||
if (orientation === "front" && design !== "default") {
|
||||
console.warn(
|
||||
"The front orientation does not support different designs.",
|
||||
);
|
||||
}
|
||||
return BOOK_SIZE_DEFAULT;
|
||||
}
|
||||
});
|
||||
|
||||
const color = $derived(
|
||||
isColorName(colorRaw)
|
||||
? Color.fromName(colorRaw).darken()
|
||||
: Color.fromCSSColor(colorRaw),
|
||||
);
|
||||
|
||||
const backgroundColor = $derived(
|
||||
design === "colored-spine"
|
||||
? color.chroma.mix("black", 0.14)
|
||||
: color.chroma,
|
||||
);
|
||||
const borderLeftColor = $derived(color.chroma.mix("white", 0.04));
|
||||
const borderRightColor = $derived(color.chroma.mix("black", 0.04));
|
||||
const bandColor = $derived(color.chroma.mix("black", 0.14));
|
||||
const textColor = $derived(new Color(backgroundColor).contrastColor.hex);
|
||||
|
||||
const verifiedWidth = $derived(normalizeWidth(width));
|
||||
const color = $derived(Color.fromName(colorName));
|
||||
const backgroundColor = $derived(color.chroma.css());
|
||||
const textColor = $derived(color.contrastColor.chroma.css());
|
||||
</script>
|
||||
|
||||
{#if orientation}
|
||||
{#if orientation === "tilted"}
|
||||
<BookTilted {design} width={verifiedWidth}>
|
||||
<Self
|
||||
{title}
|
||||
{subtitle}
|
||||
{author}
|
||||
color={color.hex}
|
||||
{design}
|
||||
{height}
|
||||
{width}
|
||||
{onClick}
|
||||
/>
|
||||
</BookTilted>
|
||||
{:else if orientation === "on-display"}
|
||||
<BookOnDisplay color={color.hex} {onClick}>
|
||||
{#if title}
|
||||
<BookText {title} {subtitle} allowWrap />
|
||||
<p class="bookshelf__book-author">By: {author}</p>
|
||||
{/if}
|
||||
</BookOnDisplay>
|
||||
{/if}
|
||||
{:else}
|
||||
<BookshelfItem>
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<div
|
||||
class="bookshelf__book-wrapper"
|
||||
class:default={design === "default"}
|
||||
class:colored-spine={design === "colored-spine"}
|
||||
class:dual-top-bands={design === "dual-top-bands"}
|
||||
class:split-bands={design === "split-bands"}
|
||||
style:--book-color={backgroundColor.css()}
|
||||
style:--book-border-left-color={borderLeftColor.css()}
|
||||
style:--book-border-right-color={borderRightColor.css()}
|
||||
style:--book-band-color={bandColor.css()}
|
||||
style:--book-width={verifiedWidth + "px"}
|
||||
style:width={verifiedWidth + "px"}
|
||||
style:color={textColor}
|
||||
class="book"
|
||||
class:tilted={orientation === "tilted"}
|
||||
class:flat={orientation === "flat"}
|
||||
class:front={orientation === "front"}
|
||||
style:--book-height="{height}px"
|
||||
style:--book-width="{width}px"
|
||||
style:--book-bg-color={backgroundColor}
|
||||
style:--book-text-color={textColor}
|
||||
style:cursor={onClick ? "pointer" : "default"}
|
||||
{role}
|
||||
{tabindex}
|
||||
onclick={onClick}
|
||||
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
|
||||
role="link"
|
||||
tabindex="0"
|
||||
onkeypress={(ev) => ev.key === "Enter" && onClick?.()}
|
||||
>
|
||||
<BookText {title} {subtitle} />
|
||||
{#if orientation !== "front" && (design === "split-bands" || design === "dual-top-bands")}
|
||||
<div class="book-band top"></div>
|
||||
{/if}
|
||||
{#if orientation !== "front" && design === "dual-top-bands"}
|
||||
<div class="book-band top2"></div>
|
||||
{/if}
|
||||
{#if orientation === "front"}
|
||||
<div class="book-crease"></div>
|
||||
{/if}
|
||||
<div class="book-inner">
|
||||
{#if title}
|
||||
<h2 class="book-title">
|
||||
{title}
|
||||
</h2>
|
||||
{#if subtitle}
|
||||
<h3 class="book-subtitle">
|
||||
{subtitle}
|
||||
</h3>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if authors && orientation === "front"}
|
||||
<p class="book-authors">By: {authors?.join(", ")}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if orientation !== "front" && design === "split-bands"}
|
||||
<div class="book-band bottom"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</BookshelfItem>
|
||||
|
||||
<style lang="scss">
|
||||
div.bookshelf__book-wrapper {
|
||||
background: var(--book-color);
|
||||
border-left: 2px solid var(--book-border-left-color);
|
||||
border-right: 2px solid var(--book-border-right-color);
|
||||
@use "./variables.scss" as bookshelf;
|
||||
|
||||
&.default,
|
||||
&.colored-spine {
|
||||
:global(.bookshelf__book-content) {
|
||||
height: calc(var(--book-width));
|
||||
margin: 0 var(--book-width);
|
||||
$small-band-width: calc(bookshelf.$book-band-width / 1.5);
|
||||
$band-color: rgba(0, 0, 0, 0.2);
|
||||
$band-top-offset: bookshelf.$book-band-offset;
|
||||
$band-top2-offset: $band-top-offset + $small-band-width +
|
||||
bookshelf.$book-band-gap;
|
||||
|
||||
// prettier-ignore
|
||||
:global(:has(.book:not(.tilted)) + :has(.book.tilted:hover):has(+ * > .book.tilted)),
|
||||
:global(:has(.book.tilted) + :has(.book.tilted:not(:hover))) {
|
||||
& > div.book.tilted {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
:global(:has(.book.tilted) + :has(.book.tilted:hover):has(+ * > .book:not(.tilted))),
|
||||
:global(:has(.book.tilted:not(:hover)):has(+ * > .book.tilted)) {
|
||||
div.book.tilted {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.book {
|
||||
box-sizing: content-box;
|
||||
position: relative;
|
||||
height: var(--book-height);
|
||||
width: var(--book-width);
|
||||
border-radius: bookshelf.$book-corner-radius;
|
||||
transition: transform 0.4s ease;
|
||||
background-color: var(--book-bg-color);
|
||||
color: var(--book-text-color);
|
||||
|
||||
.book-inner {
|
||||
--book-content-width: calc(
|
||||
var(--book-height) - #{bookshelf.$book-padding * 2}
|
||||
);
|
||||
box-sizing: border-box;
|
||||
height: var(--book-width);
|
||||
width: var(--book-height);
|
||||
padding: bookshelf.$book-padding;
|
||||
transform-origin: 0 0;
|
||||
transform: translateX(var(--book-width)) rotate(90deg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
$content-height: calc(
|
||||
var(--book-width) - #{bookshelf.$book-padding * 2}
|
||||
);
|
||||
|
||||
.book-title,
|
||||
.book-subtitle,
|
||||
.book-authors {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.book-title {
|
||||
font-size: 16px;
|
||||
height: $content-height;
|
||||
width: var(--book-content-width);
|
||||
|
||||
&:has(+ .book-subtitle) {
|
||||
height: calc(#{$content-height} / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.book-subtitle {
|
||||
font-size: 12px;
|
||||
height: calc(#{$content-height} / 2);
|
||||
width: var(--book-content-width);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.book-authors {
|
||||
font-size: 10px;
|
||||
justify-self: end;
|
||||
width: var(--book-content-width);
|
||||
}
|
||||
}
|
||||
|
||||
&.colored-spine {
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: var(--book-band-color);
|
||||
height: 100%;
|
||||
width: calc(var(--book-width));
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
transform: scale(bookshelf.$book-hover-scale);
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: -2px;
|
||||
&.tilted {
|
||||
@include bookshelf.tilt;
|
||||
|
||||
&:hover {
|
||||
transform: scale(bookshelf.$book-hover-scale);
|
||||
}
|
||||
}
|
||||
|
||||
&.dual-top-bands,
|
||||
&.split-bands {
|
||||
&:after,
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
background: var(--book-band-color);
|
||||
width: calc(100% + 4px);
|
||||
// Flat book orientation for Book Stack
|
||||
&.flat {
|
||||
height: var(--book-width);
|
||||
width: var(--book-height);
|
||||
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
.book-inner {
|
||||
height: var(--book-width);
|
||||
width: var(--book-height);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&:before {
|
||||
z-index: 2;
|
||||
}
|
||||
.book-band {
|
||||
transform: none;
|
||||
top: 0 !important;
|
||||
|
||||
:global(.bookshelf__book-content),
|
||||
:global(.fit-text) {
|
||||
height: calc(var(--book-width));
|
||||
&.top {
|
||||
left: $band-top-offset;
|
||||
|
||||
&:has(+ .top2) {
|
||||
width: $small-band-width;
|
||||
}
|
||||
}
|
||||
|
||||
&.top2 {
|
||||
width: $small-band-width;
|
||||
left: $band-top2-offset;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
left: calc(
|
||||
var(--book-height) - #{bookshelf.$book-band-offset} - #{bookshelf.$book-band-width}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.dual-top-bands {
|
||||
&:after {
|
||||
height: 10px;
|
||||
top: 8px;
|
||||
}
|
||||
// Front book orientation (cover view)
|
||||
&.front {
|
||||
$book-width: calc(var(--book-height) * 3 / 4);
|
||||
width: $book-width;
|
||||
|
||||
&:before {
|
||||
height: 15px;
|
||||
top: 26px;
|
||||
}
|
||||
.book-inner {
|
||||
$padding-left: bookshelf.$book-crease-offset +
|
||||
bookshelf.$book-crease-width + bookshelf.$book-padding;
|
||||
$padding-right: bookshelf.$book-padding;
|
||||
$padding-total: $padding-left + $padding-right;
|
||||
|
||||
:global(.bookshelf__book-content),
|
||||
:global(.fit-text) {
|
||||
width: calc(200px - 61px) !important;
|
||||
}
|
||||
--book-content-width: calc(#{$book-width} - #{$padding-total});
|
||||
transform: none;
|
||||
height: var(--book-height);
|
||||
width: $book-width;
|
||||
padding-left: $padding-left;
|
||||
|
||||
:global(.bookshelf__book-content) {
|
||||
margin: 40px var(--book-width);
|
||||
.book-title {
|
||||
height: calc(var(--book-height) / 4);
|
||||
}
|
||||
|
||||
.book-subtitle {
|
||||
height: calc(var(--book-height) / 4);
|
||||
}
|
||||
|
||||
.book-authors {
|
||||
height: calc(var(--book-height) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.split-bands {
|
||||
&:after,
|
||||
&:before {
|
||||
height: 20px;
|
||||
// Book band for styles
|
||||
.book-band {
|
||||
position: absolute;
|
||||
height: var(--book-width);
|
||||
width: bookshelf.$book-band-width;
|
||||
transform-origin: 0 0;
|
||||
transform: translateX(var(--book-width)) rotate(90deg);
|
||||
background-color: $band-color;
|
||||
|
||||
&.top {
|
||||
top: $band-top-offset;
|
||||
|
||||
&:has(+ .top2) {
|
||||
width: $small-band-width;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: 10px;
|
||||
&.top2 {
|
||||
width: $small-band-width;
|
||||
top: $band-top2-offset;
|
||||
}
|
||||
|
||||
&:before {
|
||||
bottom: 10px;
|
||||
&.bottom {
|
||||
top: calc(
|
||||
var(--book-height) - #{bookshelf.$book-band-offset} - #{bookshelf.$book-band-width}
|
||||
);
|
||||
}
|
||||
|
||||
:global(.bookshelf__book-content),
|
||||
:global(.fit-text) {
|
||||
width: calc(200px - 62px) !important;
|
||||
// Dual Top Band Offset
|
||||
& + .book-band + .book-inner {
|
||||
$padding-left: $band-top2-offset + $small-band-width +
|
||||
bookshelf.$book-padding;
|
||||
$padding-right: bookshelf.$book-padding;
|
||||
$padding-total: $padding-left + $padding-right;
|
||||
|
||||
--book-content-width: calc(
|
||||
var(--book-height) - #{$padding-total}
|
||||
);
|
||||
padding-left: $padding-left;
|
||||
}
|
||||
|
||||
:global(.bookshelf__book-content) {
|
||||
margin: 30px var(--book-width);
|
||||
// Split Band Offset
|
||||
&.top + .book-inner {
|
||||
$padding: $band-top-offset + bookshelf.$book-band-width +
|
||||
bookshelf.$book-padding;
|
||||
|
||||
--book-content-width: calc(
|
||||
var(--book-height) - #{$padding * 2}
|
||||
);
|
||||
padding-left: $padding;
|
||||
padding-right: $padding;
|
||||
}
|
||||
}
|
||||
|
||||
.book-crease {
|
||||
position: absolute;
|
||||
height: var(--book-height);
|
||||
width: bookshelf.$book-crease-width;
|
||||
left: bookshelf.$book-crease-offset;
|
||||
background-color: $band-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import chroma from "chroma-js";
|
||||
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
color?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
let { children, color = "green", onClick }: Props = $props();
|
||||
|
||||
let backgroundColor = $derived(chroma(color));
|
||||
let bandColor = $derived(chroma(color).mix("black", 0.14));
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="bookshelf__book-wrapper bookshelf__book-onDisplay"
|
||||
style:--book-color={backgroundColor.css()}
|
||||
style:--book-band-color={bandColor.css()}
|
||||
onclick={onClick}
|
||||
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
|
||||
role="link"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="book-display-crease"></div>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
$band-width: 5px;
|
||||
$band-offset: 8px;
|
||||
|
||||
div.bookshelf__book-onDisplay {
|
||||
width: 150px;
|
||||
height: 200px;
|
||||
background: var(--book-color);
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 20px 2px 10px 2px;
|
||||
position: relative;
|
||||
|
||||
padding-left: calc($band-offset + $band-width);
|
||||
padding-right: 8px;
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: var(--book-band-color);
|
||||
width: $band-width;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: $band-offset;
|
||||
}
|
||||
|
||||
&:global(.bookshelf__book-author) {
|
||||
font-size: 0.8em;
|
||||
margin-left: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,19 +1,31 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
const MAX_CHILDREN = 5;
|
||||
import BookshelfItem from "./BookshelfItem.svelte";
|
||||
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
totalChildren?: number;
|
||||
}
|
||||
|
||||
let { children, totalChildren = 0 }: Props = $props();
|
||||
let { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ul
|
||||
class="bookshelf__bookStack-wrapper"
|
||||
style:margin={`calc(20px + ${(MAX_CHILDREN - totalChildren) * 40}px) 1px 10px`}
|
||||
>
|
||||
{@render children?.()}
|
||||
</ul>
|
||||
<BookshelfItem>
|
||||
<div class="book-stack">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</BookshelfItem>
|
||||
|
||||
<style lang="scss">
|
||||
@use "./variables.scss" as bookshelf;
|
||||
|
||||
div.book-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
|
||||
:global(.bookshelf-item) {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { Color, isColorName, type ColorName } from "@utils/color";
|
||||
import BookText from "./BookText.svelte";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
color?: ColorName | string;
|
||||
design?: "default" | "split-bands" | "dual-top-bands" | "colored-spine";
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
title,
|
||||
subtitle,
|
||||
color: colorRaw = "green",
|
||||
design,
|
||||
onClick,
|
||||
}: Props = $props();
|
||||
|
||||
const color = $derived(
|
||||
isColorName(colorRaw)
|
||||
? Color.fromName(colorRaw).darken()
|
||||
: Color.fromCSSColor(colorRaw),
|
||||
);
|
||||
|
||||
const backgroundColor = $derived(
|
||||
design === "colored-spine"
|
||||
? color.chroma.mix("black", 0.14)
|
||||
: color.chroma,
|
||||
);
|
||||
const borderLeftColor = $derived(color.chroma.mix("white", 0.04));
|
||||
const borderRightColor = $derived(color.chroma.mix("black", 0.04));
|
||||
const bandColor = $derived(color.chroma.mix("black", 0.14));
|
||||
const textColor = $derived(new Color(backgroundColor).contrastColor.hex);
|
||||
</script>
|
||||
|
||||
<li class="bookshelf__bookstack-elem">
|
||||
<div
|
||||
class="bookshelf__book-wrapper"
|
||||
class:default={design === "default"}
|
||||
class:colored-spine={design === "colored-spine"}
|
||||
class:dual-top-bands={design === "dual-top-bands"}
|
||||
class:split-bands={design === "split-bands"}
|
||||
style:--book-color={backgroundColor.css()}
|
||||
style:--book-border-left-color={borderLeftColor.css()}
|
||||
style:--book-border-right-color={borderRightColor.css()}
|
||||
style:--book-band-color={bandColor.css()}
|
||||
style:color={textColor}
|
||||
onclick={onClick}
|
||||
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
|
||||
role="link"
|
||||
tabindex="0"
|
||||
>
|
||||
<BookText {title} {subtitle} />
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
background: var(--book-color);
|
||||
border-left: 2px solid var(--book-border-left-color);
|
||||
border-right: 2px solid var(--book-border-right-color);
|
||||
|
||||
&.colored-spine {
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: var(--book-band-color);
|
||||
height: 40px;
|
||||
width: calc(100% + 4px);
|
||||
border-radius: 4px;
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.split-bands,
|
||||
&.dual-top-bands {
|
||||
&:after,
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: var(--book-band-color);
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&.split-bands {
|
||||
&:after {
|
||||
width: 20px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
width: 20px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
:global(.bookshelf__book-content),
|
||||
:global(.fit-text) {
|
||||
width: calc(200px - 60px) !important;
|
||||
}
|
||||
|
||||
:global(.bookshelf__book-content) {
|
||||
margin: 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&.dual-top-bands {
|
||||
&:after {
|
||||
width: 10px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
width: 15px;
|
||||
left: 24px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
:global(.bookshelf__book-content),
|
||||
:global(.fit-text) {
|
||||
width: calc(200px - 31px) !important;
|
||||
}
|
||||
|
||||
:global(.bookshelf__book-content) {
|
||||
margin: 0 31px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,61 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { fitText } from "@ui/directives";
|
||||
import { wrap } from "module";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
allowWrap?: boolean;
|
||||
}
|
||||
|
||||
let { title, subtitle, allowWrap }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="bookshelf__book-content"
|
||||
class:center-content={subtitle === undefined}
|
||||
>
|
||||
{#if title}
|
||||
<h2
|
||||
class="bookshelf__book-title fit-text"
|
||||
class:wrap={allowWrap}
|
||||
use:fitText={{
|
||||
minFontSize: 12,
|
||||
maxFontSize: 16,
|
||||
multiLine: allowWrap,
|
||||
detectMultiLine: false,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
{/if}
|
||||
{#if subtitle}
|
||||
<h4
|
||||
class="bookshelf__book-subtitle fit-text"
|
||||
use:fitText={{
|
||||
minFontSize: 10,
|
||||
maxFontSize: 14,
|
||||
multiLine: allowWrap,
|
||||
detectMultiLine: false,
|
||||
}}
|
||||
>
|
||||
{subtitle}
|
||||
</h4>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.bookshelf__book-title,
|
||||
.bookshelf__book-subtitle {
|
||||
width: 100% !important;
|
||||
|
||||
:global(.textFitted) {
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.bookshelf__book-subtitle {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -1,51 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
width?: number;
|
||||
design?: "default" | "colored-spine" | "dual-top-bands" | "split-bands";
|
||||
topMargin?: number;
|
||||
}
|
||||
|
||||
let {
|
||||
children,
|
||||
width = 40,
|
||||
design = "default",
|
||||
topMargin,
|
||||
}: Props = $props();
|
||||
|
||||
function getTopMargin(
|
||||
design: "default" | "colored-spine" | "dual-top-bands" | "split-bands",
|
||||
): number {
|
||||
switch (design) {
|
||||
case "split-bands":
|
||||
return 30;
|
||||
case "dual-top-bands":
|
||||
return 41;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="bookshelf__book-wrapper bookshelf__book-tilted"
|
||||
style:--book-tilted-width={width + "px"}
|
||||
style:--book-tilted-top-margin={(topMargin ?? getTopMargin(design)) + "px"}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
:global(.bookshelf__book-content),
|
||||
:global(.fit-text) {
|
||||
height: calc(var(--book-tilted-width));
|
||||
width: calc(200px - 60px);
|
||||
}
|
||||
|
||||
:global(.bookshelf__book-content) {
|
||||
margin: var(--book-tilted-top-margin) var(--book-tilted-width);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,32 +1,88 @@
|
|||
<script lang="ts">
|
||||
import "./bookshelf.scss";
|
||||
import type { Snippet } from "svelte";
|
||||
import { type Snippet } from "svelte";
|
||||
import type { ActionReturn } from "svelte/action";
|
||||
import textFit from "textfit";
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
};
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
|
||||
const color = "#a47148";
|
||||
const fitAllText = (el: HTMLDivElement): ActionReturn => {
|
||||
const observer = new MutationObserver(() => {
|
||||
requestAnimationFrame(() => {
|
||||
textFit(el.getElementsByClassName("book-title"), {
|
||||
minFontSize: 8,
|
||||
maxFontSize: 16,
|
||||
multiLine: true,
|
||||
});
|
||||
|
||||
textFit(el.getElementsByClassName("book-subtitle"), {
|
||||
minFontSize: 6,
|
||||
maxFontSize: 14,
|
||||
multiLine: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(el, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
observer.disconnect();
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="bookshelf__wrapper" style:--book-shelf-color={color}>
|
||||
<div class="bookshelf" use:fitAllText>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
@use "sass:color";
|
||||
@use "./variables.scss" as bookshelf;
|
||||
|
||||
div.bookshelf {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
min-height: calc(bookshelf.$height + bookshelf.$shelf-width);
|
||||
background-image: linear-gradient(
|
||||
color-mix(in srgb, var(--book-shelf-color), black 32%),
|
||||
color-mix(in srgb, var(--book-shelf-color), black 30%) 220px,
|
||||
color-mix(in srgb, var(--book-shelf-color), white 4%) 220px,
|
||||
color-mix(in srgb, var(--book-shelf-color), white 4%) 222px,
|
||||
var(--book-shelf-color) 222px,
|
||||
var(--book-shelf-color) 228px,
|
||||
color-mix(in srgb, var(--book-shelf-color), black 4%) 228px,
|
||||
color-mix(in srgb, var(--book-shelf-color), black 4%) 230px
|
||||
color.scale(bookshelf.$color, $lightness: -32%),
|
||||
color.scale(bookshelf.$color, $lightness: -30%) bookshelf.$height,
|
||||
|
||||
color.scale(bookshelf.$color, $lightness: bookshelf.$shadow-pct)
|
||||
bookshelf.$height,
|
||||
color.scale(bookshelf.$color, $lightness: bookshelf.$shadow-pct)
|
||||
calc(bookshelf.$height + bookshelf.$shadow-width),
|
||||
bookshelf.$color bookshelf.$height,
|
||||
bookshelf.$color calc(bookshelf.$height + bookshelf.$shelf-width),
|
||||
color.scale(
|
||||
bookshelf.$color,
|
||||
$lightness: -1 * bookshelf.$shadow-pct
|
||||
)
|
||||
calc(
|
||||
bookshelf.$height + bookshelf.$shelf-width -
|
||||
bookshelf.$shadow-width
|
||||
),
|
||||
color.scale(
|
||||
bookshelf.$color,
|
||||
$lightness: -1 * bookshelf.$shadow-pct
|
||||
)
|
||||
calc(bookshelf.$height + bookshelf.$shelf-width)
|
||||
);
|
||||
border: 10px var(--book-shelf-color) solid;
|
||||
background-size: 10px calc(bookshelf.$height + bookshelf.$shelf-width);
|
||||
border: 10px bookshelf.$color solid;
|
||||
padding-left: bookshelf.$book-spacing;
|
||||
padding-right: bookshelf.$book-spacing;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: bookshelf.$shelf-width bookshelf.$book-spacing;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="bookshelf-item">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "./variables.scss" as bookshelf;
|
||||
|
||||
div.bookshelf-item {
|
||||
box-sizing: border-box;
|
||||
height: bookshelf.$height;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
|
||||
// Why????
|
||||
padding-bottom: 0.75px;
|
||||
&:global(:has(.book.tilted)) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,217 +0,0 @@
|
|||
$white: white;
|
||||
$black: black;
|
||||
$background: #dedede;
|
||||
|
||||
$bookWidth: 40px;
|
||||
$bookHeight: 200px;
|
||||
$bookEdge: 2px;
|
||||
|
||||
.bookshelf__wrapper {
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
background-size: 10px 230px;
|
||||
min-height: 230px;
|
||||
|
||||
.bookshelf__bookStack-wrapper {
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
flex-flow: column nowrap;
|
||||
list-style: none;
|
||||
float: left;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.bookshelf__bookStack-outOfStock {
|
||||
background: #232323;
|
||||
color: #fff;
|
||||
height: 150px;
|
||||
padding: 0;
|
||||
margin: 50px 1px auto 1px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.25px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.bookshelf__bookstack-elem {
|
||||
margin-inline-start: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.bookshelf__book-wrapper {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.bookshelf__book-content {
|
||||
height: $bookWidth;
|
||||
width: $bookHeight;
|
||||
|
||||
transform-origin: 0% 0%;
|
||||
transform: rotate(0deg);
|
||||
overflow: hidden;
|
||||
|
||||
// * Centering content
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.bookshelf__book-title,
|
||||
.bookshelf__book-subtitle {
|
||||
font-size: 0.8em;
|
||||
font-weight: 600;
|
||||
|
||||
height: calc($bookWidth / 2);
|
||||
width: $bookHeight;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
// * Centering content
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bookshelf__book-subtitle {
|
||||
font-size: 0.6em;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.center-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// .bookshelf__book-title{
|
||||
// width: 140px;
|
||||
// height: 40px;
|
||||
// transform: rotate(0deg);
|
||||
// margin-left: 29px;
|
||||
// margin-top: 0;
|
||||
// text-align: center;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bookshelf__book-wrapper {
|
||||
height: $bookHeight;
|
||||
width: $bookWidth;
|
||||
float: left;
|
||||
margin: 20px 1px 10px 1px;
|
||||
border-radius: 6px;
|
||||
transition: transform 0.4s ease;
|
||||
position: relative;
|
||||
|
||||
.bookshelf__book-content {
|
||||
width: $bookHeight;
|
||||
|
||||
transform-origin: 0% 0%;
|
||||
transform: rotate(90deg);
|
||||
overflow: hidden;
|
||||
|
||||
// * Centering content
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.bookshelf__book-title,
|
||||
.bookshelf__book-subtitle {
|
||||
font-size: 0.8em;
|
||||
font-weight: 600;
|
||||
|
||||
height: calc($bookWidth / 2);
|
||||
width: $bookHeight;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
// * Centering content
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bookshelf__book-subtitle {
|
||||
font-size: 0.6em;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.center-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.bookshelf__book-tilted {
|
||||
float: left;
|
||||
width: 72px;
|
||||
|
||||
.bookshelf__book-wrapper {
|
||||
--book-width: 40px !important;
|
||||
width: 40px !important;
|
||||
}
|
||||
|
||||
&:hover .bookshelf__book-wrapper {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
& > .bookshelf__book-wrapper {
|
||||
transform: translateY(-22px) translateX(13px) rotate(9deg);
|
||||
}
|
||||
}
|
||||
|
||||
.bookshelf__book-onDisplay {
|
||||
.bookshelf__book-content {
|
||||
width: calc(100% - 11px);
|
||||
margin-left: 11px;
|
||||
transform: rotate(0deg);
|
||||
overflow: visible;
|
||||
|
||||
.bookshelf__book-title,
|
||||
.bookshelf__book-subtitle {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bookshelf__book-title {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.bookshelf__book-subtitle {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.bookshelf__book-author {
|
||||
font-size: 0.8em;
|
||||
margin-left: 13px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
@use "sass:math";
|
||||
|
||||
$color: #a47148;
|
||||
$height: 220px;
|
||||
$shelf-width: 10px;
|
||||
$shadow-pct: 4%;
|
||||
$shadow-width: calc($shelf-width / 5);
|
||||
$book-spacing: 2px;
|
||||
$book-padding: 4px;
|
||||
$book-corner-radius: 3.5px;
|
||||
$book-hover-scale: 1.02;
|
||||
$book-band-offset: 10px;
|
||||
$book-band-gap: 5px;
|
||||
$book-band-width: 10px;
|
||||
$book-crease-offset: 10px;
|
||||
$book-crease-width: 2px;
|
||||
$tilt-angle: 9deg;
|
||||
|
||||
@mixin tilt() {
|
||||
$width: var(--book-width);
|
||||
$height: var(--book-height);
|
||||
$sin: math.sin($tilt-angle);
|
||||
$cos: math.cos($tilt-angle);
|
||||
|
||||
// $newWidth: calc(abs($width * $cos) + abs($height * $sin));
|
||||
$newWidth: calc(
|
||||
max(($width * $cos), -1 * ($width * $cos)) +
|
||||
max(($height * $sin), -1 * ($height * $sin))
|
||||
);
|
||||
$margin-h: calc(($newWidth - $width) / 2);
|
||||
|
||||
// $newHeight: calc(abs($width * $sin) + abs($height * $cos));
|
||||
$newHeight: calc(
|
||||
max(($width * $sin), -1 * ($width * $sin)) +
|
||||
max(($height * $cos), -1 * ($height * $cos))
|
||||
);
|
||||
$margin-v: calc(($newHeight - $height) / 2);
|
||||
|
||||
transform: rotate($tilt-angle);
|
||||
margin: $margin-v $margin-h;
|
||||
transition: transform 0.4s ease, margin 0.4s ease;
|
||||
|
||||
&:hover {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import type { ActionReturn } from "svelte/action";
|
||||
import { default as doTextFit, type TextFitOption } from "textfit";
|
||||
|
||||
export function fitText(
|
||||
el: HTMLElement,
|
||||
opts: TextFitOption = {}
|
||||
): ActionReturn<TextFitOption> {
|
||||
doTextFit(el, opts);
|
||||
|
||||
return {
|
||||
update(opts) {
|
||||
doTextFit(el, opts);
|
||||
},
|
||||
destroy() {},
|
||||
};
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
export { chart } from "./chart";
|
||||
export { clickOutside } from "./clickOutside";
|
||||
export { fitText } from "./fitText";
|
||||
|
|
Loading…
Reference in New Issue