thibaud frere commited on
Commit
542395c
·
1 Parent(s): a4f9298

add brotli compression

Browse files
Dockerfile CHANGED
@@ -23,6 +23,9 @@ RUN npm run export:pdf -- --theme=light --wait=full
23
  # Use an official Nginx runtime as the base image for serving the application
24
  FROM nginx:alpine
25
 
 
 
 
26
  # Copy the built application from the build stage
27
  COPY --from=build /app/dist /usr/share/nginx/html
28
 
 
23
  # Use an official Nginx runtime as the base image for serving the application
24
  FROM nginx:alpine
25
 
26
+ # Install Brotli dynamic module for Nginx
27
+ RUN apk add --no-cache nginx-mod-http-brotli
28
+
29
  # Copy the built application from the build stage
30
  COPY --from=build /app/dist /usr/share/nginx/html
31
 
app/astro.config.mjs CHANGED
@@ -1,6 +1,7 @@
1
  import { defineConfig } from 'astro/config';
2
  import mdx from '@astrojs/mdx';
3
  import mermaid from 'astro-mermaid';
 
4
  import remarkMath from 'remark-math';
5
  import rehypeKatex from 'rehype-katex';
6
  import remarkFootnotes from 'remark-footnotes';
@@ -11,7 +12,12 @@ import rehypeCitation from 'rehype-citation';
11
 
12
  export default defineConfig({
13
  output: 'static',
14
- integrations: [mermaid({ theme: 'forest', autoTheme: true }), mdx()],
 
 
 
 
 
15
  devToolbar: {
16
  enabled: false
17
  },
 
1
  import { defineConfig } from 'astro/config';
2
  import mdx from '@astrojs/mdx';
3
  import mermaid from 'astro-mermaid';
4
+ import compressor from 'astro-compressor';
5
  import remarkMath from 'remark-math';
6
  import rehypeKatex from 'rehype-katex';
7
  import remarkFootnotes from 'remark-footnotes';
 
12
 
13
  export default defineConfig({
14
  output: 'static',
15
+ integrations: [
16
+ mermaid({ theme: 'forest', autoTheme: true }),
17
+ mdx(),
18
+ // Precompress output with Brotli (preferred) and Gzip as fallback
19
+ compressor({ brotli: true, gzip: true })
20
+ ],
21
  devToolbar: {
22
  enabled: false
23
  },
app/package.json CHANGED
Binary files a/app/package.json and b/app/package.json differ
 
app/src/components/Accordion.astro ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ const { title, open = false, class: className, ...props } = Astro.props;
3
+ const wrapperClass = ["accordion", className].filter(Boolean).join(" ");
4
+ ---
5
+ <details class={wrapperClass} open={open} {...props}>
6
+ <summary class="accordion__summary">
7
+ <span class="accordion__title">{title}</span>
8
+ <svg class="accordion__chevron" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
9
+ <path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
10
+ </svg>
11
+ </summary>
12
+ <div class="accordion__content-wrapper">
13
+ <div class="accordion__content">
14
+ <slot />
15
+ </div>
16
+ </div>
17
+ </details>
18
+
19
+ <script>
20
+ // Animate open/close by transitioning explicit height
21
+ document.addEventListener('DOMContentLoaded', () => {
22
+ const accordions = document.querySelectorAll('.accordion') as NodeListOf<HTMLDetailsElement>;
23
+ accordions.forEach((acc) => {
24
+ const summary = acc.querySelector('summary.accordion__summary') as HTMLElement | null;
25
+ const wrapper = acc.querySelector('.accordion__content-wrapper') as HTMLElement | null;
26
+ const content = acc.querySelector('.accordion__content') as HTMLElement | null;
27
+ if (!summary || !wrapper || !content) return;
28
+
29
+ const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
30
+ const duration = reduceMotion ? 0 : 240;
31
+
32
+ // Initial state
33
+ wrapper.style.overflow = 'hidden';
34
+ wrapper.style.height = acc.open ? 'auto' : '0px';
35
+
36
+ const open = () => {
37
+ wrapper.style.height = '0px';
38
+ void wrapper.offsetHeight; // reflow
39
+ const target = content.scrollHeight;
40
+ wrapper.style.transition = `height ${duration}ms ease`;
41
+ wrapper.style.height = `${target}px`;
42
+ const onEnd = () => {
43
+ wrapper.style.transition = '';
44
+ wrapper.style.height = 'auto';
45
+ wrapper.removeEventListener('transitionend', onEnd);
46
+ };
47
+ wrapper.addEventListener('transitionend', onEnd);
48
+ };
49
+
50
+ const close = () => {
51
+ const start = content.scrollHeight;
52
+ wrapper.style.height = `${start}px`;
53
+ void wrapper.offsetHeight; // reflow
54
+ wrapper.style.transition = `height ${duration}ms ease`;
55
+ wrapper.style.height = '0px';
56
+ const onEnd = () => {
57
+ wrapper.style.transition = '';
58
+ wrapper.removeEventListener('transitionend', onEnd);
59
+ acc.removeAttribute('open');
60
+ };
61
+ wrapper.addEventListener('transitionend', onEnd);
62
+ };
63
+
64
+ summary.addEventListener('click', (e) => {
65
+ e.preventDefault();
66
+ if (acc.open) {
67
+ close();
68
+ } else {
69
+ acc.setAttribute('open', '');
70
+ open();
71
+ }
72
+ });
73
+ });
74
+ });
75
+ </script>
76
+
77
+ <style>
78
+ .accordion {
79
+ border: 1px solid var(--border-color);
80
+ border-radius: 10px;
81
+ background: var(--surface-bg);
82
+ transition: box-shadow 180ms ease, border-color 180ms ease;
83
+ }
84
+
85
+ .accordion[open] {
86
+ box-shadow: 0 6px 22px rgba(0,0,0,0.08);
87
+ border-color: color-mix(in oklab, var(--border-color), var(--primary-color) 20%);
88
+ }
89
+
90
+ .accordion__summary {
91
+ list-style: none;
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ gap: var(--spacing-1);
96
+ padding: var(--spacing-1);
97
+ cursor: pointer;
98
+ color: var(--text-color);
99
+ user-select: none;
100
+ }
101
+
102
+ /* Remove native marker */
103
+ .accordion__summary::-webkit-details-marker {
104
+ display: none;
105
+ }
106
+ .accordion__summary::marker {
107
+ content: "";
108
+ }
109
+
110
+ .accordion__title {
111
+ font-weight: 600;
112
+ }
113
+
114
+ .accordion__chevron {
115
+ flex: 0 0 auto;
116
+ transition: transform 220ms ease;
117
+ opacity: .85;
118
+ }
119
+
120
+ .accordion[open] .accordion__chevron {
121
+ transform: rotate(180deg);
122
+ }
123
+
124
+ /* Animated expand/collapse using height (controlled in JS) */
125
+ .accordion__content-wrapper {
126
+ overflow: hidden;
127
+ height: 0px;
128
+ will-change: height;
129
+ }
130
+
131
+ .accordion__content {
132
+ padding: var(--spacing-1);
133
+ }
134
+
135
+ /* Focus styles for accessibility */
136
+ .accordion__summary:focus-visible {
137
+ outline: 2px solid var(--primary-color);
138
+ outline-offset: 3px;
139
+ border-radius: 8px;
140
+ }
141
+ </style>
142
+
143
+
app/src/content/article.mdx CHANGED
@@ -60,13 +60,10 @@ import GettingStarted from "./chapters/getting-started.mdx";
60
  <span className="tag">SEO Friendly</span>
61
  <span className="tag">Mermaid diagrams</span>
62
  <span className="tag">Lightweight bundle</span>
63
- <span className="tag">Aside notes</span>
64
  <span className="tag">Mobile friendly</span>
65
  <span className="tag">Optimized images</span>
66
- <span className="tag">Medium like zoomable images</span>
67
- <span className="tag">PDF export</span>
68
- <span className="tag">To do: Accordion</span>
69
- <span className="tag">To do: dataviz color palette generator</span>
70
  </div>
71
  <Fragment slot="aside">
72
  If you have questions or remarks open a discussion on the <a href="https://huggingface.co/spaces/tfrere/research-blog-template/discussions?status=open&type=discussion">Community tab</a>!
 
60
  <span className="tag">SEO Friendly</span>
61
  <span className="tag">Mermaid diagrams</span>
62
  <span className="tag">Lightweight bundle</span>
 
63
  <span className="tag">Mobile friendly</span>
64
  <span className="tag">Optimized images</span>
65
+ <span className="tag">Automatic PDF export</span>
66
+ <span className="tag">Dataviz color palettes</span>
 
 
67
  </div>
68
  <Fragment slot="aside">
69
  If you have questions or remarks open a discussion on the <a href="https://huggingface.co/spaces/tfrere/research-blog-template/discussions?status=open&type=discussion">Community tab</a>!
app/src/content/chapters/available-blocks.mdx CHANGED
@@ -5,6 +5,7 @@ import HtmlFragment from '../../components/HtmlFragment.astro';
5
  import Aside from '../../components/Aside.astro';
6
  import Wide from '../../components/Wide.astro';
7
  import FullBleed from '../../components/FullBleed.astro';
 
8
 
9
  ## Available blocks
10
 
@@ -18,6 +19,7 @@ All the following blocks are available in the article.mdx file. You can also cre
18
  <a className="button" href="#mermaid-diagrams">Mermaid</a>
19
  <a className="button" href="#citations-and-notes">Citations & notes</a>
20
  <a className="button" href="#placement">Placement</a>
 
21
  <a className="button" href="#minimal-table">Minimal table</a>
22
  <a className="button" href="#audio">Audio</a>
23
  <a className="button" href="#embeds">Embeds</a>
@@ -39,9 +41,7 @@ $x^2 + y^2 = z^2$
39
 
40
  **Block**
41
 
42
- $$
43
- \mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V
44
- $$
45
 
46
  <small className="muted">Example</small>
47
  ```mdx
@@ -213,6 +213,39 @@ import FullBleed from '../components/FullBleed.astro'
213
  ```
214
 
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  ### Minimal table
217
 
218
  | Method | Score |
@@ -230,17 +263,13 @@ import FullBleed from '../components/FullBleed.astro'
230
 
231
  ### Audio
232
 
233
- <audio controls src={audioDemo}>
234
- Your browser does not support the audio element.
235
- </audio>
236
-
237
  <small className="muted">Example</small>
238
  ```mdx
239
  import audioDemo from '../assets/audio/audio-example.wav'
240
 
241
- <audio controls src={audioDemo}>
242
- Your browser does not support the audio element.
243
- </audio>
244
  ```
245
 
246
 
 
5
  import Aside from '../../components/Aside.astro';
6
  import Wide from '../../components/Wide.astro';
7
  import FullBleed from '../../components/FullBleed.astro';
8
+ import Accordion from '../../components/Accordion.astro';
9
 
10
  ## Available blocks
11
 
 
19
  <a className="button" href="#mermaid-diagrams">Mermaid</a>
20
  <a className="button" href="#citations-and-notes">Citations & notes</a>
21
  <a className="button" href="#placement">Placement</a>
22
+ <a className="button" href="#accordion">Accordion</a>
23
  <a className="button" href="#minimal-table">Minimal table</a>
24
  <a className="button" href="#audio">Audio</a>
25
  <a className="button" href="#embeds">Embeds</a>
 
41
 
42
  **Block**
43
 
44
+ Affichage en bloc, voir l'exemple ci-dessous.
 
 
45
 
46
  <small className="muted">Example</small>
47
  ```mdx
 
213
  ```
214
 
215
 
216
+ ### Accordion
217
+
218
+ Accessible accordion based on `details/summary`. You can pass any children content.
219
+
220
+ <Accordion title="What can this accordion hold?" open>
221
+ <p>Text, lists, images, code blocks, etc.</p>
222
+ <ul>
223
+ <li>Item one</li>
224
+ <li>Item two</li>
225
+ </ul>
226
+ </Accordion>
227
+
228
+ <Accordion title="Closed by default">
229
+ <p>This one stays collapsed until the user clicks the summary.</p>
230
+ </Accordion>
231
+
232
+ <small className="muted">Example</small>
233
+ ```mdx
234
+ import Accordion from '../components/Accordion.astro'
235
+
236
+ <Accordion title="Accordion title" open>
237
+ <p>Free content with <strong>markdown</strong> and MDX components.</p>
238
+ </Accordion>
239
+
240
+ <Accordion title="Another accordion">
241
+ <ul>
242
+ <li>Item A</li>
243
+ <li>Item B</li>
244
+ </ul>
245
+ </Accordion>
246
+ ```
247
+
248
+
249
  ### Minimal table
250
 
251
  | Method | Score |
 
263
 
264
  ### Audio
265
 
266
+ <audio controls src={audioDemo}/>
267
+ <br/>
 
 
268
  <small className="muted">Example</small>
269
  ```mdx
270
  import audioDemo from '../assets/audio/audio-example.wav'
271
 
272
+ <audio controls src={audioDemo}/>
 
 
273
  ```
274
 
275
 
nginx.conf CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  worker_processes auto;
2
  pid /tmp/nginx.pid;
3
 
@@ -17,6 +20,40 @@ http {
17
  sendfile on;
18
  keepalive_timeout 65;
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  server {
21
  listen 8080;
22
  server_name localhost;
 
1
+ load_module modules/ngx_http_brotli_filter_module.so;
2
+ load_module modules/ngx_http_brotli_static_module.so;
3
+
4
  worker_processes auto;
5
  pid /tmp/nginx.pid;
6
 
 
20
  sendfile on;
21
  keepalive_timeout 65;
22
 
23
+ # Brotli (preferred)
24
+ brotli on;
25
+ brotli_comp_level 5;
26
+ brotli_static always;
27
+ brotli_min_length 256;
28
+ brotli_types
29
+ text/plain
30
+ text/css
31
+ text/javascript
32
+ application/javascript
33
+ application/json
34
+ application/xml
35
+ application/rss+xml
36
+ image/svg+xml
37
+ font/ttf
38
+ font/otf
39
+ font/woff
40
+ font/woff2;
41
+
42
+ # Gzip fallback
43
+ gzip on;
44
+ gzip_vary on;
45
+ gzip_proxied any;
46
+ gzip_comp_level 5;
47
+ gzip_min_length 256;
48
+ gzip_types
49
+ text/plain
50
+ text/css
51
+ application/javascript
52
+ application/json
53
+ application/xml
54
+ application/rss+xml
55
+ image/svg+xml;
56
+
57
  server {
58
  listen 8080;
59
  server_name localhost;