chore: updated /art-calendar 8a6052f8
Steve · 2026-05-08 22:49 1 file(s) · +145 −97
src/pages/art-calendar.astro +145 −97
5 5
import { getFormattedDate } from "@/utils";
6 6
7 7
type Artwork = {
8 -
	date: string;
9 -
	artwork_id: number;
10 -
	title: string;
11 -
	artist_display: string | null;
12 -
	date_display: string | null;
13 -
	medium_display: string | null;
14 -
	dimensions: string | null;
15 -
	place_of_origin: string | null;
16 -
	credit_line: string | null;
17 -
	short_description: string | null;
18 -
	image_id: string | null;
19 -
	image_url: string | null;
20 -
	source_url: string | null;
8 +
  date: string;
9 +
  artwork_id: number;
10 +
  title: string;
11 +
  artist_display: string | null;
12 +
  date_display: string | null;
13 +
  medium_display: string | null;
14 +
  dimensions: string | null;
15 +
  place_of_origin: string | null;
16 +
  credit_line: string | null;
17 +
  short_description: string | null;
18 +
  image_id: string | null;
19 +
  image_url: string | null;
20 +
  source_url: string | null;
21 21
};
22 22
23 23
const meta = {
24 -
	title: "Art Calendar",
25 -
	description:
26 -
		"A different painting every day from the Art Institute of Chicago.",
24 +
  title: "Art Calendar",
25 +
  description:
26 +
    "A different painting every day from the Art Institute of Chicago.",
27 27
};
28 28
29 29
const FEED_URL = "https://easel.stevedylan.dev/feed.xml";
33 33
let error: string | null = null;
34 34
35 35
try {
36 -
	const res = await fetch("https://easel.stevedylan.dev/api/today", {
37 -
		headers: { Accept: "application/json" },
38 -
	});
39 -
	if (!res.ok) throw new Error(`easel returned ${res.status}`);
40 -
	artwork = (await res.json()) as Artwork;
36 +
  const res = await fetch("https://easel.stevedylan.dev/api/today", {
37 +
    headers: { Accept: "application/json" },
38 +
  });
39 +
  if (!res.ok) throw new Error(`easel returned ${res.status}`);
40 +
  artwork = (await res.json()) as Artwork;
41 41
} catch (e) {
42 -
	error = e instanceof Error ? e.message : "Failed to reach easel API";
42 +
  error = e instanceof Error ? e.message : "Failed to reach easel API";
43 43
}
44 44
45 45
Astro.response.headers.set(
46 -
	"Cache-Control",
47 -
	"public, max-age=3600, s-maxage=86400",
46 +
  "Cache-Control",
47 +
  "public, max-age=3600, s-maxage=86400",
48 48
);
49 49
---
50 50
51 51
<PageLayout meta={meta}>
52 -
	<div class="flex min-h-screen flex-col items-start justify-start gap-6">
53 -
		<div class="flex flex-col gap-1">
54 -
			<h1 class="title">Art Calendar</h1>
55 -
			<p class="text-sm opacity-70">
56 -
				One painting every day from the
57 -
				<a class="style-link" href="https://www.artic.edu/" target="_blank" rel="noopener">Art Institute of Chicago</a>.
58 -
				{getFormattedDate(today)}.
59 -
			</p>
60 -
			<p class="text-sm opacity-70 flex items-center gap-1">
61 -
				<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256" class="shrink-0 text-accent-2" aria-hidden="true">
62 -
					<path fill="currentColor" d="M106.91 149.09A71.53 71.53 0 0 1 128 200a8 8 0 0 1-16 0a56 56 0 0 0-56-56a8 8 0 0 1 0-16a71.53 71.53 0 0 1 50.91 21.09M56 80a8 8 0 0 0 0 16a104 104 0 0 1 104 104a8 8 0 0 0 16 0A120 120 0 0 0 56 80m118.79 1.21A166.9 166.9 0 0 0 56 32a8 8 0 0 0 0 16a151 151 0 0 1 107.48 44.52A151 151 0 0 1 208 200a8 8 0 0 0 16 0a166.9 166.9 0 0 0-49.21-118.79M60 184a12 12 0 1 0 12 12a12 12 0 0 0-12-12"/>
63 -
				</svg>
64 -
				<a class="style-link" href={FEED_URL} target="_blank" rel="noopener">RSS feed</a>
65 -
			</p>
66 -
		</div>
67 -
		{error || !artwork ? (
68 -
			<p class="text-red-400 text-sm">Couldn't load today's painting{error ? `: ${error}` : ""}.</p>
69 -
		) : (
70 -
			<article class="flex flex-col w-full gap-6">
71 -
				{artwork.image_url && (
72 -
					<img
73 -
						src={artwork.image_url}
74 -
						alt={artwork.title}
75 -
						loading="eager"
76 -
						class="w-full max-w-2xl h-auto"
77 -
					/>
78 -
				)}
79 -
				<div class="flex flex-col gap-1">
80 -
					<h2 class="text-xl italic text-accent-2">{artwork.title}</h2>
81 -
					{artwork.artist_display && (
82 -
						<p class="text-sm whitespace-pre-line">{artwork.artist_display}</p>
83 -
					)}
84 -
					{artwork.date_display && (
85 -
						<p class="text-sm opacity-70">{artwork.date_display}</p>
86 -
					)}
87 -
				</div>
88 -
				<dl class="grid grid-cols-1 sm:grid-cols-[max-content_1fr] gap-x-6 gap-y-2 text-sm">
89 -
					{artwork.place_of_origin && (
90 -
						<>
91 -
							<dt class="opacity-50 uppercase tracking-widest text-xs">Origin</dt>
92 -
							<dd>{artwork.place_of_origin}</dd>
93 -
						</>
94 -
					)}
95 -
					{artwork.medium_display && (
96 -
						<>
97 -
							<dt class="opacity-50 uppercase tracking-widest text-xs">Medium</dt>
98 -
							<dd>{artwork.medium_display}</dd>
99 -
						</>
100 -
					)}
101 -
					{artwork.dimensions && (
102 -
						<>
103 -
							<dt class="opacity-50 uppercase tracking-widest text-xs">Dimensions</dt>
104 -
							<dd>{artwork.dimensions}</dd>
105 -
						</>
106 -
					)}
107 -
					{artwork.credit_line && (
108 -
						<>
109 -
							<dt class="opacity-50 uppercase tracking-widest text-xs">Credit</dt>
110 -
							<dd>{artwork.credit_line}</dd>
111 -
						</>
112 -
					)}
113 -
				</dl>
114 -
				{artwork.short_description && (
115 -
					<p class="text-sm">{artwork.short_description}</p>
116 -
				)}
117 -
				{artwork.source_url && (
118 -
					<a class="style-link self-start" href={artwork.source_url} target="_blank" rel="noopener">
119 -
						View on artic.edu
120 -
					</a>
121 -
				)}
122 -
			</article>
123 -
		)}
124 -
	</div>
52 +
  <div class="flex min-h-screen flex-col items-start justify-start gap-6">
53 +
    <div class="flex flex-col gap-1 w-full">
54 +
      <div class="flex flex-row justify-between items-center w-full">
55 +
        <h1 class="title">Art Calendar</h1>
56 +
        <div class="flex gap-2 items-center">
57 +
          <a
58 +
            class="inline-block p-2 sm:hover:text-link"
59 +
            href={FEED_URL}
60 +
            target="_blank"
61 +
            rel="noopener noreferrer"
62 +
          >
63 +
            <svg
64 +
              xmlns="http://www.w3.org/2000/svg"
65 +
              class="h-6 w-6"
66 +
              viewBox="0 0 24 24"
67 +
              stroke-width="1.5"
68 +
              stroke="currentColor"
69 +
              fill="none"
70 +
              stroke-linecap="round"
71 +
              stroke-linejoin="round"
72 +
            >
73 +
              <g
74 +
                fill="none"
75 +
                stroke="currentColor"
76 +
                stroke-linecap="round"
77 +
                stroke-linejoin="round"
78 +
                stroke-width="2"
79 +
                ><path d="M4 11a9 9 0 0 1 9 9M4 4a16 16 0 0 1 16 16"
80 +
                ></path><circle cx="5" cy="19" r="1"></circle></g
81 +
              >
82 +
            </svg>
83 +
            <span class="sr-only">RSS</span>
84 +
          </a>
85 +
        </div>
86 +
      </div>
87 +
      <p class="text-sm opacity-70">
88 +
        {getFormattedDate(today)}
89 +
      </p>
90 +
    </div>
91 +
    {
92 +
      error || !artwork ? (
93 +
        <p class="text-red-400 text-sm">
94 +
          Couldn't load today's painting{error ? `: ${error}` : ""}.
95 +
        </p>
96 +
      ) : (
97 +
        <article class="flex flex-col w-full gap-6">
98 +
          {artwork.image_url && (
99 +
            <img
100 +
              src={artwork.image_url}
101 +
              alt={artwork.title}
102 +
              loading="eager"
103 +
              class="w-full h-auto"
104 +
            />
105 +
          )}
106 +
          <div class="flex flex-col gap-1">
107 +
            <h2 class="text-xl italic text-accent-2">{artwork.title}</h2>
108 +
            {artwork.artist_display && (
109 +
              <p class="text-sm whitespace-pre-line">
110 +
                {artwork.artist_display}
111 +
              </p>
112 +
            )}
113 +
          </div>
114 +
          <dl class="grid grid-cols-1 sm:grid-cols-[max-content_1fr] gap-x-6 gap-y-2 text-sm">
115 +
            {artwork.date_display && (
116 +
              <>
117 +
                <dt class="opacity-50 uppercase tracking-widest text-xs">
118 +
                  Date
119 +
                </dt>
120 +
                <dd>{artwork.date_display}</dd>
121 +
              </>
122 +
            )}
123 +
            {artwork.place_of_origin && (
124 +
              <>
125 +
                <dt class="opacity-50 uppercase tracking-widest text-xs">
126 +
                  Origin
127 +
                </dt>
128 +
                <dd>{artwork.place_of_origin}</dd>
129 +
              </>
130 +
            )}
131 +
            {artwork.medium_display && (
132 +
              <>
133 +
                <dt class="opacity-50 uppercase tracking-widest text-xs">
134 +
                  Medium
135 +
                </dt>
136 +
                <dd>{artwork.medium_display}</dd>
137 +
              </>
138 +
            )}
139 +
            {artwork.dimensions && (
140 +
              <>
141 +
                <dt class="opacity-50 uppercase tracking-widest text-xs">
142 +
                  Dimensions
143 +
                </dt>
144 +
                <dd>{artwork.dimensions}</dd>
145 +
              </>
146 +
            )}
147 +
            {artwork.credit_line && (
148 +
              <>
149 +
                <dt class="opacity-50 uppercase tracking-widest text-xs">
150 +
                  Credit
151 +
                </dt>
152 +
                <dd>{artwork.credit_line}</dd>
153 +
              </>
154 +
            )}
155 +
          </dl>
156 +
          {artwork.short_description && (
157 +
            <p class="text-sm">{artwork.short_description}</p>
158 +
          )}
159 +
          {artwork.source_url && (
160 +
            <a
161 +
              class="style-link self-start"
162 +
              href={artwork.source_url}
163 +
              target="_blank"
164 +
              rel="noopener"
165 +
            >
166 +
              View on artic.edu
167 +
            </a>
168 +
          )}
169 +
        </article>
170 +
      )
171 +
    }
172 +
  </div>
125 173
</PageLayout>