@adaptive-sm/astro-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +183 -0
  3. package/lib/button/Button.astro +58 -0
  4. package/lib/button/buttonCva.ts +196 -0
  5. package/lib/button/buttonIconCva.ts +52 -0
  6. package/lib/button/classesButtonClickAnimation.ts +8 -0
  7. package/lib/button/classesButtonClickAnimationPush.ts +6 -0
  8. package/lib/button/classesButtonClickAnimationSquish.ts +6 -0
  9. package/lib/button/classesButtonDisabled.ts +1 -0
  10. package/lib/card/CardWrapper.astro +15 -0
  11. package/lib/card/classesCardWrapper.ts +16 -0
  12. package/lib/details/Details.astro +57 -0
  13. package/lib/dev/TailwindIndicator.astro +28 -0
  14. package/lib/form/Fieldset.astro +21 -0
  15. package/lib/form/classesFieldset.ts +1 -0
  16. package/lib/generate_ai_rules/generate_agent_rules.bash +9 -0
  17. package/lib/generate_ai_rules/generate_agent_rules_1_lib.bash +33 -0
  18. package/lib/generate_ai_rules/generate_agent_rules_2_copy.bash +14 -0
  19. package/lib/generate_ai_rules/generate_agent_rules_3_combine.bash +31 -0
  20. package/lib/generate_demo_list/DemoList.astro +31 -0
  21. package/lib/generate_demo_list/DemoListType.ts +1 -0
  22. package/lib/generate_demo_list/generateDemoList.ts +55 -0
  23. package/lib/generate_image_list/generateImageList.ts +101 -0
  24. package/lib/grid/classesGridCols.ts +86 -0
  25. package/lib/icon/Icon1.astro +21 -0
  26. package/lib/img/ImageType.ts +6 -0
  27. package/lib/img/Img.astro +27 -0
  28. package/lib/img/TypedImg.astro +26 -0
  29. package/lib/img/classInvertDiagram.ts +1 -0
  30. package/lib/img/classesImgZoomInOnHover.ts +1 -0
  31. package/lib/layouts/MarkdownWrapper.astro +17 -0
  32. package/lib/layouts/MinimalLayout.astro +67 -0
  33. package/lib/layouts/parts/ThemeToggle.astro +137 -0
  34. package/lib/layouts/parts/astroElementId.ts +7 -0
  35. package/lib/layouts/parts/markdown.css +93 -0
  36. package/lib/link/LinkButton.astro +48 -0
  37. package/lib/link/LinkText.astro +23 -0
  38. package/lib/link/classesTextLink.ts +13 -0
  39. package/lib/list/BlackBulletPoint.astro +16 -0
  40. package/lib/list/BlackBulletPoints.astro +23 -0
  41. package/lib/list/CheckPoint.astro +20 -0
  42. package/lib/list/CheckPoints.astro +23 -0
  43. package/lib/list/NumberedList.astro +14 -0
  44. package/lib/list/Ps.astro +12 -0
  45. package/lib/list/TextOrLink.astro +19 -0
  46. package/lib/modal/Modal.astro +54 -0
  47. package/lib/modal/Modal.module.css +19 -0
  48. package/lib/modal/ModalButton.astro +41 -0
  49. package/lib/modal/modal.ts +137 -0
  50. package/lib/page/PageCentered.astro +14 -0
  51. package/lib/page/PageCenteredCard.astro +17 -0
  52. package/lib/page/classesBg.ts +27 -0
  53. package/lib/page/classesPageCentered.ts +6 -0
  54. package/lib/popover/Popover1.astro +45 -0
  55. package/lib/popover/setupPopoverListeners.ts +22 -0
  56. package/lib/select/Select.astro +45 -0
  57. package/lib/table/DesktopTableClassses.ts +6 -0
  58. package/lib/table/MobileTableClassses.ts +7 -0
  59. package/lib/table/Table.astro +41 -0
  60. package/lib/table/TableColumnDef.ts +10 -0
  61. package/lib/table/TableD.astro +55 -0
  62. package/lib/table/TableM.astro +22 -0
  63. package/lib/table/TableMEntry.astro +27 -0
  64. package/lib/table/sharedTableRowClasses.ts +1 -0
  65. package/lib/table/tableVisibilityClasses.ts +28 -0
  66. package/lib/text/classesTextGray.ts +1 -0
  67. package/lib/text/classesTextHeader.ts +1 -0
  68. package/lib/utils/bun/BunCmd.ts +7 -0
  69. package/lib/utils/bun/cryAndTryAgainLater.ts +6 -0
  70. package/lib/utils/bun/logBunCmd.ts +1 -0
  71. package/lib/utils/bun/runCmdAsync.ts +44 -0
  72. package/lib/utils/bun/runCmdLocally.ts +13 -0
  73. package/lib/utils/obj/objectKeys.ts +21 -0
  74. package/lib/utils/ran/generateId12.ts +7 -0
  75. package/lib/utils/ran/generateId3.ts +7 -0
  76. package/lib/utils/ran/generateId4.ts +7 -0
  77. package/lib/utils/ran/generateId5.ts +7 -0
  78. package/lib/utils/ran/generateId6.ts +7 -0
  79. package/lib/utils/ran/generateId7.ts +7 -0
  80. package/lib/utils/ran/generateReadableId.ts +35 -0
  81. package/lib/utils/ran/urlAlphabet32.ts +8 -0
  82. package/lib/utils/ui/classArr.ts +3 -0
  83. package/lib/utils/ui/classMerge.ts +10 -0
  84. package/lib/utils/ui/isDevEnv.ts +7 -0
  85. package/lib/utils/ui/tailwindBreakpoint.ts +83 -0
  86. package/package.json +56 -0
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -x # Print all executed commands to the terminal
4
+ set -e # Exit immediately if a command exits with a non-zero status
5
+
6
+ scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
7
+ bash "$scriptDir"/generate_agent_rules_1_lib.bash
8
+ bash "$scriptDir"/generate_agent_rules_2_copy.bash
9
+ bash "$scriptDir"/generate_agent_rules_3_combine.bash
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ set -e # Exit immediately if a command exits with a non-zero status
3
+ set -x # Print all executed commands to the terminal
4
+
5
+ scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
6
+ # libDir="lib"
7
+ libDir="$scriptDir/../"
8
+ localRulesDir=".roo/rules-code"
9
+ localMdFile=$(readlink -f $localRulesDir/astro_lib.md)
10
+
11
+ mkdir -p "$localRulesDir"
12
+
13
+ if [ ! -d "$libDir" ]; then
14
+ echo "Error: Directory $libDir does not exist." >&2
15
+ exit 1
16
+ fi
17
+
18
+ cat > "$localMdFile" << EOF
19
+ # Available Astro UI Components and utility functions
20
+
21
+ These components from @adaptive-sm/astro-ui can be imported using the \`~/\` alias.
22
+ Generated at: $(date +"%Y-%m-%d %H:%M")
23
+
24
+ EOF
25
+
26
+ foundFiles=$(find "$libDir" \( -name "*.astro" -o -name "*.ts" -o -name "*.tsx" \) -printf "%P\n")
27
+ if [ -z "$foundFiles" ]; then
28
+ echo "Error: No .astro, .ts, or .tsx files found in $libDir" >&2
29
+ rm -f "$localMdFile"
30
+ exit 1
31
+ fi
32
+ echo "$foundFiles" | sed 's#^#- ~/#' >> "$localMdFile"
33
+ # echo "Found $(echo "$foundFiles" | wc -l) files."
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ set -e # Exit immediately if a command exits with a non-zero status
3
+ set -x # Print all executed commands to the terminal
4
+
5
+ srcDir="node_modules/@adaptive-sm/astro-ui/.roo"
6
+ # srcDir="/home/david/Coding/adaptive-astro-ui/.roo/rules-code"
7
+ dstDir=".roo"
8
+
9
+ # create rules dir if missing
10
+ mkdir -p "$dstDir"
11
+ # copy files
12
+ rsync -av "$srcDir/rules-code/" "$dstDir/rules-code/"
13
+ rsync -av "$srcDir/rules-ask/" "$dstDir/rules-ask/"
14
+ rsync -av "$srcDir/mcp.json" "$dstDir/mcp.json"
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ set -x # Print all executed commands to the terminal
3
+ set -e # Exit immediately if a command exits with a non-zero status
4
+
5
+ shopt -s nullglob # glob returns nothing if no matches
6
+
7
+ srcDir="./.roo/rules-code"
8
+
9
+ # combine md files into AGENTS.md
10
+ outfile="AGENTS.md"
11
+ sep=$'\n\n---\n\n'
12
+
13
+ # Start with a fresh output file
14
+ : > "$outfile"
15
+
16
+ first=true
17
+ for md in $srcDir/*.md; do
18
+ if [[ -f "$md" ]]; then
19
+ # Only prepend the separator *between* files, not before the first one
20
+ if [[ "$first" == true ]]; then
21
+ first=false
22
+ else
23
+ printf %s "$sep" >> "$outfile"
24
+ fi
25
+ cat "$md" >> "$outfile"
26
+ fi
27
+ done
28
+
29
+ # Count and print the total number of lines in the resulting file
30
+ total_lines=$(wc -l < "$outfile")
31
+ echo "$outfile: $total_lines lines"
@@ -0,0 +1,31 @@
1
+ ---
2
+ import { classesTextLink } from "~/link/classesTextLink"
3
+ import { classArr } from "~/utils/ui/classArr"
4
+ import { type DemoListType } from "./DemoListType"
5
+
6
+ interface Props {
7
+ demos: DemoListType
8
+ class?: string
9
+ }
10
+ const props = Astro.props
11
+
12
+ // Extract categories and their demo pages
13
+ const categories = Object.entries(props.demos)
14
+ ---
15
+
16
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
17
+ {
18
+ categories.map(([category, pages]) => (
19
+ <section class="bg-white dark:bg-zinc-700 rounded-lg shadow-md p-4 border border-gray-200 dark:border-zinc-600">
20
+ <h2 class="text-xl font-bold mb-2 capitalize">{category.replace(/_/g, " ")}</h2>
21
+ <ul class="list-disc pl-4 space-y-1">
22
+ {pages.map((page) => (
23
+ <li class={classArr(classesTextLink)}>
24
+ <a href={`/demos/${category}/${page}`}>{page.replace(/_/g, " ").replaceAll("demo ", "")}</a>
25
+ </li>
26
+ ))}
27
+ </ul>
28
+ </section>
29
+ ))
30
+ }
31
+ </div>
@@ -0,0 +1 @@
1
+ export type DemoListType = Record<string, string[]>
@@ -0,0 +1,55 @@
1
+ import { readdir, writeFile } from "node:fs/promises"
2
+ import { join } from "node:path"
3
+ import { runCmdAsync } from "~/utils/bun/runCmdAsync"
4
+ import type { DemoListType } from "./DemoListType"
5
+
6
+ export async function generateDemoList(demosPath: string, outputPath: string) {
7
+ const categories = await readdir(demosPath, { withFileTypes: true })
8
+
9
+ const demoPageList: DemoListType = {}
10
+
11
+ for (const category of categories) {
12
+ if (category.isDirectory()) {
13
+ const categoryPath = join(demosPath, category.name)
14
+ const demoFiles = await readdir(categoryPath)
15
+ const files = demoFiles.filter(isDemoFile).map((file) => file.replace(/\.astro$/, ""))
16
+ if (files.length > 0) {
17
+ demoPageList[category.name] = files
18
+ }
19
+ }
20
+ }
21
+ sortDemoPageList(demoPageList)
22
+
23
+ const outputContent = `import type { DemoListType } from "~/generate_demo_list/DemoListType"
24
+
25
+ export const demoList = ${JSON.stringify(demoPageList, null, 2)} satisfies DemoListType;
26
+ `
27
+
28
+ await writeFile(outputPath, outputContent, "utf-8")
29
+ await formatGeneratedCodeFile(outputPath)
30
+ }
31
+
32
+ function isDemoFile(file: string) {
33
+ if (!file.endsWith(".astro")) return false
34
+ if (!file.startsWith("demo")) return false
35
+ return true
36
+ }
37
+
38
+ function sortDemoPageList(demoPageList: DemoListType) {
39
+ for (const list of Object.values(demoPageList)) {
40
+ sortStringsByLengthThenByCharactersInPlace(list)
41
+ }
42
+ }
43
+
44
+ function sortStringsByLengthThenByCharactersInPlace(arr: string[]): string[] {
45
+ return arr.sort((a, b) => a.length - b.length || a.localeCompare(b))
46
+ }
47
+
48
+ function sortStringsByLengthThenByCharacters(arr: string[]): string[] {
49
+ return sortStringsByLengthThenByCharactersInPlace([...arr])
50
+ }
51
+
52
+ async function formatGeneratedCodeFile(outputPath: string) {
53
+ const cmd = `bun run biome check --write ${outputPath}`.split(" ")
54
+ runCmdAsync(cmd)
55
+ }
@@ -0,0 +1,101 @@
1
+ import imageSize from "image-size"
2
+ import { promises as fs } from "node:fs"
3
+ import path from "node:path"
4
+ import type { ImageType } from "~/img/ImageType"
5
+ import { runCmdAsync } from "~/utils/bun/runCmdAsync"
6
+
7
+ const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".avif", ".tiff", ".svg"])
8
+
9
+ export async function generateImageList(
10
+ imageDirectory: string,
11
+ existingImages: Record<string, ImageType>,
12
+ outputPath: string,
13
+ ) {
14
+ // console.log("found", existingImages.length, "existing images")
15
+ const imageMap = await processImageFiles(imageDirectory, existingImages)
16
+ const sorted = sortImageMap(imageMap)
17
+ await writeGeneratedImagesFile(sorted, outputPath)
18
+ await formatGeneratedImagesCodeFile(outputPath)
19
+ }
20
+
21
+ async function writeGeneratedImagesFile(imageMap: Record<string, ImageType>, outputPath: string): Promise<void> {
22
+ const outputContent = `
23
+ import type { ImageType } from "~/img/ImageType"
24
+ // Auto-generated, manual changes will be lost
25
+ export const imageList = ${JSON.stringify(imageMap, null, 2)} as const satisfies Record<string, ImageType>;
26
+ `
27
+ await Bun.write(outputPath, outputContent)
28
+ console.log(`Generated ${Object.keys(imageMap).length} images to ${outputPath}`)
29
+ }
30
+
31
+ async function processImageFiles(
32
+ directory: string,
33
+ existingImages: Record<string, any>,
34
+ ): Promise<Record<string, ImageType>> {
35
+ const imageMap: Record<string, ImageType> = {}
36
+
37
+ for await (const filePath of walkDirectory(directory)) {
38
+ const ext = path.extname(filePath).toLowerCase()
39
+ if (!IMAGE_EXTENSIONS.has(ext)) {
40
+ console.log("ignoring " + ext, filePath)
41
+ continue
42
+ }
43
+
44
+ try {
45
+ const buffer = await fs.readFile(filePath)
46
+ const dimensions = imageSize(buffer)
47
+ if (!dimensions.width || !dimensions.height) continue
48
+
49
+ const relativePath = path.relative(directory, filePath)
50
+ const fileName = path.basename(filePath, ext)
51
+ let key = fileName.replace(/-/g, "_")
52
+ if (/^\d/.test(fileName)) {
53
+ key = "i" + key
54
+ }
55
+
56
+ const prevAlt = existingImages[key]?.alt
57
+ const alt = prevAlt || fileName.replace(/[-_]/g, " ")
58
+
59
+ imageMap[key] = {
60
+ path: relativePath,
61
+ width: dimensions.width,
62
+ height: dimensions.height,
63
+ alt,
64
+ }
65
+ } catch (error) {
66
+ console.error(`Error processing ${filePath}:`, error)
67
+ }
68
+ }
69
+
70
+ return imageMap
71
+ }
72
+
73
+ async function* walkDirectory(dir: string): AsyncGenerator<string> {
74
+ const entries = await fs.readdir(dir, { withFileTypes: true })
75
+ for (const entry of entries) {
76
+ const fullPath = path.join(dir, entry.name)
77
+ if (entry.isDirectory()) {
78
+ yield* walkDirectory(fullPath)
79
+ } else if (entry.isFile()) {
80
+ yield fullPath
81
+ }
82
+ }
83
+ }
84
+
85
+ function sortImageMap(m: Record<string, ImageType>): Record<string, ImageType> {
86
+ return Object.keys(m)
87
+ .sort()
88
+ .reduce(
89
+ (sorted, key) => {
90
+ // We know key exists in m since we're iterating its keys
91
+ sorted[key] = m[key]!
92
+ return sorted
93
+ },
94
+ {} as Record<string, ImageType>,
95
+ )
96
+ }
97
+
98
+ async function formatGeneratedImagesCodeFile(outputPath: string) {
99
+ const cmd = `bun run biome check --write ${outputPath}`.split(" ")
100
+ runCmdAsync(cmd)
101
+ }
@@ -0,0 +1,86 @@
1
+ import { classArr } from "~/utils/ui/classArr"
2
+
3
+ export const classesGridCols2ContentFr = "grid grid-cols-[max-content_1fr]"
4
+ export const classesGridCols3MaxMinFr = "grid grid-cols-[max-content_min-content_1fr]"
5
+
6
+ /**
7
+ * ~600 px (37rem, ~60ch) / col
8
+ */
9
+ export const classesGridCols2xl = classArr("grid grid-cols-1", "xl:grid-cols-2")
10
+ export const classesGridCols2xl3 = classArr(classesGridCols2xl, "2xl:grid-cols-4", "3xl:grid-cols-6")
11
+
12
+ /**
13
+ * ~400 px (25rem, ~40ch) / col
14
+ */
15
+ export const classesGridCols3xl = classArr("grid grid-cols-1", "md:grid-cols-2", "xl:grid-cols-3")
16
+ export const classesGridCols3xl3 = classArr(classesGridCols3xl, "2xl:grid-cols-6", "3xl:grid-cols-9")
17
+
18
+ /**
19
+ * ~320 px (18rem, ~32ch) / col
20
+ * 4 = 4x1, 2x2
21
+ * xl: 1280/320 = 4
22
+ * lg: 1024/320 = 3.2
23
+ * md: 768/320 = 2.4
24
+ * sm: 640/320 = 2
25
+ * xs: 400/320 = 1.25
26
+ */
27
+ export const classesGridCols4xl = classArr("grid grid-cols-1", "sm:grid-cols-2", "md:grid-cols-2", "xl:grid-cols-4")
28
+ export const classesGridCols4xl3 = classArr(classesGridCols4xl, "2xl:grid-cols-8", "3xl:grid-cols-12")
29
+
30
+ /**
31
+ * ~256 px (16rem, ~26ch) / col
32
+ * 5 = 5x1
33
+ * xl: 1280/256 = 5
34
+ * lg: 1024/256 = 4
35
+ * md: 768/256 = 3
36
+ * sm: 640/256 = 2.5
37
+ * xs: 400/256 = 1.56
38
+ */
39
+ export const classesGridCols5xl = classArr("grid grid-cols-1", "sm:grid-cols-2", "md:grid-cols-3", "lg:grid-cols-4", "xl:grid-cols-5")
40
+ export const classesGridCols5xl3 = classArr(classesGridCols5xl, "2xl:grid-cols-10", "3xl:grid-cols-15")
41
+
42
+ /**
43
+ *
44
+ * ~200 px (13rem, ~20ch) / col
45
+ * 6 = 6x1, 3x2, 2x3
46
+ * xl: 1280/200 = 6.4
47
+ * lg: 1024/200 = 5.12
48
+ * md: 768/200 = 3.84
49
+ * sm: 640/200 = 3.2
50
+ * xs: 400/200 = 2
51
+ */
52
+ export const classesGridCols6xl = classArr("grid grid-cols-2", "sm:grid-cols-3", "md:grid-cols-4", "xl:grid-cols-6")
53
+ export const classesGridCols6xl3 = classArr(classesGridCols6xl, "2xl:grid-cols-12", "3xl:grid-cols-18")
54
+
55
+ /**
56
+ *
57
+ * ~160 px (9rem, ~16ch) / col
58
+ * 8 = 8x1, 4x2, 2x4
59
+ * xl: 1280/160 = 8
60
+ * lg: 1024/160 = 6.4
61
+ * md: 768/160 = 4.8
62
+ * sm: 640/160 = 4
63
+ * xs: 400/160 = 2.5
64
+ */
65
+ export const classesGridCols8xl = classArr(
66
+ "grid grid-cols-3",
67
+ "sm:grid-cols-4",
68
+ "md:grid-cols-5",
69
+ "lg:grid-cols-6",
70
+ "xl:grid-cols-8",
71
+ )
72
+ export const classesGridCols8xl3 = classArr(classesGridCols8xl, "2xl:grid-cols-16", "3xl:grid-cols-24")
73
+
74
+ export function classesGridColsByCharacterLength(chars: number): string {
75
+ if (chars < 16) return classesGridCols8xl
76
+ if (chars < 20) return classesGridCols6xl
77
+ if (chars < 32) return classesGridCols4xl
78
+ if (chars < 40) return classesGridCols3xl
79
+ return classesGridCols2xl
80
+ }
81
+
82
+ export function classesGridColsByCharacterLengthMaxLgDialog(chars: number): string {
83
+ if (chars < 20) return "grid " + "grid-cols-2 " + "sm:grid-cols-3 " + "md:grid-cols-4 " + "lg:grid-cols-5 "
84
+ if (chars < 40) return "grid " + "grid-cols-1 " + "md:grid-cols-2 "
85
+ return "grid " + "grid-cols-1 "
86
+ }
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { classMerge } from "~/utils/ui/classMerge"
3
+
4
+ interface Props {
5
+ path: string
6
+ class?: string
7
+ title?: string
8
+ ariaHidden?: boolean
9
+ }
10
+ const props = Astro.props
11
+ const ariaHidden = props.ariaHidden ?? !props.title
12
+ // TODO: maybe re-add title={title}
13
+ ---
14
+
15
+ <svg
16
+ viewBox={"0 0 24 24"}
17
+ aria-hidden={ariaHidden}
18
+ class={classMerge("size-6 align-middle dark:fill-white", props.class)}
19
+ >
20
+ <path d={props.path}></path>
21
+ </svg>
@@ -0,0 +1,6 @@
1
+ export type ImageType = {
2
+ path: string
3
+ height: number
4
+ width: number
5
+ alt: string
6
+ }
@@ -0,0 +1,27 @@
1
+ ---
2
+ import { classesImgZoomInOnHover } from "./classesImgZoomInOnHover"
3
+ import { classMerge } from "~/utils/ui/classMerge"
4
+
5
+ interface Props {
6
+ alt: string
7
+ src: string
8
+ zoomIn?: boolean
9
+ class?: string
10
+ size?: number
11
+ width?: number | null | undefined
12
+ height?: number | null | undefined
13
+ loadingLazy?:boolean
14
+ decodeAsync?:boolean
15
+ }
16
+ const p = Astro.props
17
+ ---
18
+
19
+ <img
20
+ src={p.src}
21
+ alt={p.alt}
22
+ class={classMerge(p.zoomIn && classesImgZoomInOnHover, p.class)}
23
+ loading={p.loadingLazy ?? true ? "lazy" : undefined}
24
+ decoding={p.decodeAsync ?? true ? "async" : undefined}
25
+ width={p.size ?? p.width}
26
+ height={p.size ?? p.height}
27
+ />
@@ -0,0 +1,26 @@
1
+ ---
2
+ import { classArr } from "~/utils/ui/classArr"
3
+ import type { ImageType } from "./ImageType"
4
+ import Img from "./Img.astro"
5
+ import { classInvertDiagram } from "~/img/classInvertDiagram"
6
+
7
+ interface Props {
8
+ img: ImageType
9
+ zoomIn?: boolean
10
+ class?: string
11
+ }
12
+ const props = Astro.props
13
+ const isSvg = props.img.path.endsWith(".svg")
14
+
15
+ function imageUrl(p: string) {
16
+ return "/media-b2/" + p
17
+ }
18
+ ---
19
+
20
+ <Img
21
+ src={imageUrl(props.img.path)}
22
+ alt={props.img.alt}
23
+ class={classArr(isSvg ? classInvertDiagram : "", props.class)}
24
+ width={isSvg ? null : props.img.width}
25
+ height={isSvg ? null : props.img.height}
26
+ />
@@ -0,0 +1 @@
1
+ export const classInvertDiagram = "invert-diagram"
@@ -0,0 +1 @@
1
+ export const classesImgZoomInOnHover = "transform transition-transform duration-200 -translate-x-3 hover:scale-110"
@@ -0,0 +1,17 @@
1
+ ---
2
+ import PageCenteredCard from "~/page/PageCenteredCard.astro"
3
+ import { classMerge } from "~/utils/ui/classMerge"
4
+ import { classArr } from "~/utils/ui/classArr"
5
+
6
+ import "./parts/markdown.css"
7
+
8
+ interface Props {
9
+ class?: string
10
+ }
11
+
12
+ const p = Astro.props
13
+ ---
14
+
15
+ <PageCenteredCard class={classMerge("max-w-5xl my-12", p.class)} classInner={classArr("markdown-body", p.class)}>
16
+ <slot />
17
+ </PageCenteredCard>
@@ -0,0 +1,67 @@
1
+ ---
2
+ import { classArr } from "../utils/ui/classArr"
3
+ import { objectKeys } from "~/utils/obj/objectKeys"
4
+ import TailwindIndicator from "~/dev/TailwindIndicator.astro"
5
+
6
+ export interface Props {
7
+ title: string
8
+ description?: string
9
+ meta?: Record<string, string>
10
+ class?: string
11
+ }
12
+
13
+ const props = Astro.props
14
+ ---
15
+
16
+ <!doctype html>
17
+ <html lang="en">
18
+ <head>
19
+ <!-- Base -->
20
+ <meta charset="UTF-8" />
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
22
+ <meta name="generator" content={Astro.generator} />
23
+
24
+ <!-- Site -->
25
+ <title>{props.title}</title>
26
+ {props.description && <meta name="description" content={props.description} />}
27
+
28
+ <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
29
+
30
+ <!-- Icons -->
31
+ <link rel="icon" type="image/svg+xml" href="/logo.svg" />
32
+ <link rel="icon" sizes="32x32" href="/favicon.ico" />
33
+ <link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png" />
34
+ <link rel="manifest" href="/site.webmanifest" />
35
+
36
+ <!-- Crawlers -->
37
+ <meta name="robots" content="index, follow" />
38
+ <meta name="googlebot" content="index, follow" />
39
+
40
+ {
41
+ props.meta && (
42
+ <Fragment>
43
+ {objectKeys(props.meta).map((k) => (
44
+ <meta property={k} content={props.meta![k]} />
45
+ ))}
46
+ </Fragment>
47
+ )
48
+ }
49
+ </head>
50
+ <body class="min-h-dvh w-full font-sans antialiased bg-background text-foreground">
51
+ <slot name="navbar" />
52
+ <main
53
+ id="main"
54
+ class={classArr(
55
+ "relative min-h-dvh",
56
+ "w-full",
57
+ "flex flex-col items-center",
58
+ props.class,
59
+ //
60
+ )}
61
+ >
62
+ <slot />
63
+ </main>
64
+ <slot name="footer" />
65
+ <TailwindIndicator />
66
+ </body>
67
+ </html>