+ ) : (
+
+ )}
+ >
+ );
+}
diff --git a/website/src/components/strudel/MiniRepl.astro b/website/src/components/strudel/MiniRepl.astro
new file mode 100644
index 00000000..84d38800
--- /dev/null
+++ b/website/src/components/strudel/MiniRepl.astro
@@ -0,0 +1,7 @@
+---
+import { MiniRepl } from './MiniRepl';
+const { tune } = Astro.props;
+import '@strudel.cycles/react/dist/style.css';
+---
+
+
diff --git a/website/src/components/strudel/MiniRepl.jsx b/website/src/components/strudel/MiniRepl.jsx
new file mode 100644
index 00000000..ee32e0db
--- /dev/null
+++ b/website/src/components/strudel/MiniRepl.jsx
@@ -0,0 +1,25 @@
+import { evalScope, controls } from '@strudel.cycles/core';
+import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
+import { samples } from '@strudel.cycles/webaudio';
+
+fetch('https://strudel.tidalcycles.org/EmuSP12.json')
+ .then((res) => res.json())
+ .then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/'));
+
+evalScope(
+ controls,
+ import('@strudel.cycles/core'),
+ // import('@strudel.cycles/tone'),
+ import('@strudel.cycles/tonal'),
+ import('@strudel.cycles/mini'),
+ import('@strudel.cycles/midi'),
+ import('@strudel.cycles/xen'),
+ import('@strudel.cycles/webaudio'),
+ import('@strudel.cycles/osc'),
+);
+
+// prebake();
+
+export function MiniRepl({ tune }) {
+ return <_MiniRepl tune={tune} hideOutsideView={true} />;
+}
diff --git a/website/src/components/tutorial.mdx b/website/src/components/tutorial.mdx
new file mode 100644
index 00000000..de1e0d47
--- /dev/null
+++ b/website/src/components/tutorial.mdx
@@ -0,0 +1,950 @@
+---
+title: What is Strudel?
+description: Strudel Tutorial
+layout: ../layouts/MainLayout.astro
+---
+
+# What is Strudel?
+
+With Strudel, you can expressively write dynamic music pieces.
+It aims to be [Tidal Cycles](https://tidalcycles.org/) for JavaScript (started by the same author).
+
+You don't need to know JavaScript or Tidal Cycles to make music with Strudel.
+
+This interactive tutorial will guide you through the basics of Strudel.
+
+The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/).
+
+## Show me a Demo
+
+To get a taste of what Strudel can do, check out this track:
+
+],hh(3,4)") // drums
+.speed(perlin.range(.7,.9)) // random sample speed variation
+,"" // bassline
+.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
+.add(perlin.range(0,.5)) // random pitch variation
+.superimpose(add(.05)) // add second, slightly detuned voice
+.n() // wrap in "n"
+.decay(.15).sustain(0) // make each note of equal length
+.s('sawtooth') // waveform
+.gain(.4) // turn down
+.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
+,">".voicings('lefthand') // chords
+.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
+.add(perlin.range(0,.5)) // random pitch variation
+.n() // wrap in "n"
+.s('sawtooth') // waveform
+.gain(.16) // turn down
+.cutoff(500) // fixed cutoff
+.attack(1) // slowly fade in
+)
+.slow(3/2)`}
+/>
+
+## Disclaimer
+
+- This project is still in its experimental state. In the future, parts of it might change significantly.
+- This tutorial is far from complete.
+
+
+
+# Playing Pitches
+
+Pitches are an essential building block for music. In Strudel, there are 3 different options to express a pitch:
+
+- `note`: letter notation
+- `n`: number notation
+- `freq`: frequency notation
+
+## note
+
+Notes are notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`.
+
+
+
+By the way, you can edit the contents of the player, and press "update" to hear your change!
+You can also press "play" on the next player without needing to stop the last one.
+
+## n
+
+If you don't like notes, you can also use numbers with `n` instead:
+
+
+
+These numbers are interpreted as so called midi numbers, where adjacent whole numbers are 1 semitone apart.
+You could also write decimal numbers to get microtonal pitches:
+
+
+
+## freq
+
+To get maximum freedom, you can also use `freq` to directly control the frequency:
+
+
+
+In this example, we play A3 (220Hz), C#4 natural (275Hz), E4 (330Hz) and A4 (440Hz).
+
+
+
+# Playing Sounds
+
+Instead of pitches, we can also play sounds with `s`:
+
+
+
+Similarly, we can also use `s` to change the sound of our pitches:
+
+
+
+Try changing the sound to `square`, `triangle` or `sine`!
+
+We will go into the defails of sounds and synths [later](http://localhost:3000/tutorial/#web-audio-output).
+
+
+
+# Syntax
+
+So far, we've seen the following syntax:
+
+```
+xxx("foo").yyy("bar")
+```
+
+Generally, `xxx` and `yyy` are called functions, while `foo` and `bar` are called function arguments.
+So far, we've used the functions to declare which aspect of the sound we want to control, and their arguments for the actual data.
+The `yyy` function is called a chained function, because it is appended with a dot.
+
+Strudel makes heavy use of chained functions. Here is a more extreme example:
+
+
+
+The `//` is a line comment, resulting in the `delay` function being ignored.
+It is a handy way to quickly turn stuff on and off. Try uncommenting this line by deleting `//`!
+
+The good news is, that this covers 99% of the JavaScript syntax needed for Strudel!
+
+Let's now look at the way we can express rhythms..
+
+
+
+# Mini Notation
+
+Similar to Tidal Cycles, Strudel has an embedded mini language that is designed to write rhythmic patterns in a short manner.
+Before diving deeper into the details, here is a flavor of how the mini language looks like:
+
+
+
+The snippet above is enclosed in backticks (`), which allows you to write multi-line strings.
+You can also use double quotes (") for single line mini notation.
+
+## Sequences
+
+We can play more notes by separating them with spaces:
+
+
+
+Here, those four notes are squashed into one cycle, so each note is a quarter second long.
+Try adding or removing notes and notice how the tempo changes!
+
+## Division
+
+We can slow the sequence down by enclosing it in brackets and dividing it by a number:
+
+
+
+The division by two means that the sequence will be played over the course of two cycles.
+You can also use decimal numbers for any tempo you like.
+
+## Angle Brackets
+
+Using angle brackets, we can define the sequence length based on the number of children:
+
+")`} />
+
+The above snippet is the same as:
+
+
+
+The advantage of the angle brackets, is that we can add more children without needing to change the number at the end.
+
+## Multiplication
+
+Contrary to division, a sequence can be sped up by multiplying it by a number:
+
+
+
+The multiplication by 2 here means that the sequence will play twice a cycle.
+
+## Bracket Nesting
+
+To create more interesting rhythms, you can nest sequences with brackets, like this:
+
+
+
+## Rests
+
+The "~" represents a rest:
+
+
+
+## Parallel
+
+Using commas, we can play chords:
+
+
+
+To play multiple chords in a sequence, we have to wrap them in brackets:
+
+")`} />
+
+## Elongation
+
+With the "@" symbol, we can specify temporal "weight" of a sequence child:
+
+")`} />
+
+Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
+
+## Replication
+
+Using "!" we can repeat without speeding up:
+
+")`} />
+
+In essence, the `x!n` is like a shortcut for `[x*n]@n`.
+
+## Euclidian
+
+Using round brackets, we can create rhythmical sub-divisions based on three parameters: beats, segments and offset.
+The first parameter controls how may beats will be played.
+The second parameter controls the total amount of segments the beats will be distributed over.
+The third (optional) parameter controls the starting position for distributing the beats.
+One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,0)" or simply "(3,8)",
+resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segments, starting on position 1).
+
+
+
+
+
+# Synths, Samples & Effects
+
+Let's take a closer look at how we can control synths, sounds and effects.
+
+## Synths
+
+So far, all the mini notation examples all used the same sound, which is kind of boring.
+We can change the sound, using the `s` function:
+
+>").s('sawtooth')`} />
+
+Here, we are wrapping our notes inside `note` and set the sound using `s`, connected by a dot.
+
+Those functions are only 2 of many ways to alter the properties, or _params_ of a sound.
+The power of patterns allows us to sequence any _param_ independently:
+
+>").s("")`} />
+
+Now we not only pattern the notes, but the sound as well!
+`sawtooth` `square` and `triangle` are the basic waveforms available in `s`.
+
+### Envelope
+
+You can control the envelope of a synth using the `attack`, `decay`, `sustain` and `release` functions:
+
+>").s('sawtooth')
+ .attack(.1).decay(.1).sustain(.2).release(.1)`}
+/>
+
+## Samples
+
+Besides Synths, `s` can also play back samples:
+
+
+
+To know which sounds are available, open the [default sample map](https://strudel.tidalcycles.org/EmuSP12.json)
+
+### Custom Sample Maps
+
+You can load your own sample map like this:
+
+
+
+The `samples` function takes an object that maps sound names to audio file paths.
+The second argument is the base URL that comes before each path. Make sure your base URL ends with a slash, while your sample paths do **not** begin with one.
+
+Because github is a popular choice to dump samples, there is a shortcut for that:
+
+
+
+The format is `github:user/repo/branch/`.
+
+### Multiple Samples per Sound
+
+It is also possible, to declare multiple files for one sound, using the array notation:
+
+,~ ,[hh:0 hh:1]*2")`}
+/>
+
+The `:0` `:1` etc. are the indices of the array.
+The sample number can also be set using `n`:
+
+")`}
+/>
+
+### Pitched Sounds
+
+For pitched sounds, you can use `note`, just like with synths:
+
+@2").s('gtr').gain(.5)`}
+/>
+
+Here, the guitar samples will overlap, because they always play till the end.
+If we want them to behave more like a synth, we can add `clip(1)`:
+
+@2").s('gtr').clip(1)
+ .gain(.5)`}
+/>
+
+### Base Pitch
+
+If we have 2 samples with different base pitches, we can make them in tune by specifying the pitch like this:
+
+@2").s("gtr,moog").clip(1)
+ .gain(.5)`}
+/>
+
+If a sample has no pitch set, `c3` is the default.
+
+We can also declare different samples for different regions of the keyboard:
+
+!2, g4 f4]>")
+ .s('moog').clip(1)
+ .gain(.5)`}
+/>
+
+The sampler will always pick the closest matching sample for the current note!
+
+## Sampler Effects
+
+### Pattern.begin
+
+
+
+### Pattern.end
+
+
+
+### Pattern.loopAt
+
+
+
+### Pattern.chop
+
+
+
+## Audio Effects
+
+Wether you're using a synth or a sample, you can apply these effects:
+
+### gain
+
+
+
+### velocity
+
+
+
+### cutoff
+
+
+
+### resonance
+
+
+
+### hcutoff
+
+
+
+### hresonance
+
+
+
+### bandf
+
+
+
+### bandq
+
+
+
+### vowel
+
+
+
+### pan
+
+
+
+### coarse
+
+
+
+### shape
+
+
+
+### crush
+
+
+
+
+
+# JavaScript API
+
+While the mini notation is powerful on its own, there is much more to discover.
+Internally, the mini notation will expand to use the actual functional JavaScript API.
+
+For example, this Pattern in Mini Notation:
+
+
+
+is equivalent to this Pattern without Mini Notation:
+
+
+
+Similarly, there is an equivalent function for every aspect of the mini notation.
+
+Which representation to use is a matter of context. As a rule of thumb, you can think of the JavaScript API
+to fit better for the larger context, while mini notation is more practical for individiual rhythms.
+
+## Limits of Mini Notation
+
+While the Mini Notation is a powerful way to write rhythms shortly, it also has its limits. Take this example:
+
+
+
+Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them.
+While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds.
+
+## Notes
+
+Notes are automatically available as variables:
+
+
+
+An important difference to the mini notation:
+For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name.
+
+The above is the same as:
+
+
+
+Using strings, you can also use "#".
+
+## Alternative Syntax
+
+In the above example, we are nesting a function inside a function, which makes reading the parens a little more difficult.
+To avoid getting to many nested parens, there is an alternative syntax to add a type to a pattern:
+
+
+
+You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty!
+
+## Pattern Factories
+
+The following functions will return a pattern.
+
+
+### cat
+
+
+
+### seq
+
+
+
+### stack
+
+
+
+### timeCat
+
+
+
+## Combining Patterns
+
+You can freely mix JS patterns, mini patterns and values! For example, this pattern:
+
+
+
+...is equivalent to:
+
+
+
+... as well as:
+
+")`} />
+
+While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @.
+When using JS patterns, there is a lot more you can do.
+
+## Time Modifiers
+
+The following functions modify a pattern temporal structure in some way.
+
+### Pattern.slow
+
+
+
+### Pattern.fast
+
+
+
+### Pattern.early
+
+
+
+### Pattern.late
+
+
+
+### Pattern.legato
+
+
+
+### Pattern.struct
+
+
+
+### Pattern.euclid
+
+
+
+### Pattern.euclidLegato
+
+
+
+### Pattern.rev
+
+
+
+### Pattern.iter
+
+
+
+### Pattern.iterBack
+
+
+
+## Conditional Modifiers
+
+### Pattern.every
+
+
+
+### Pattern.when
+
+
+
+## Accumulation Modifiers
+
+### Pattern.stack
+
+
+
+### Pattern.superimpose
+
+
+
+### Pattern.layer
+
+
+
+### Pattern.off
+
+
+
+### Pattern.echo
+
+
+
+### Pattern.echoWith
+
+
+
+## Concat Modifiers
+
+### Pattern.seq
+
+
+
+### Pattern.cat
+
+
+
+## Value Modifiers
+
+### Pattern.add
+
+
+
+### Pattern.sub
+
+
+
+### Pattern.mul
+
+
+
+### Pattern.div
+
+
+
+### Pattern.round
+
+
+
+### Pattern.apply
+
+
+
+### Pattern.range
+
+
+
+### Pattern.chunk
+
+
+
+### Pattern.chunkBack
+
+
+
+## Continuous Signals
+
+Signals are patterns with continuous values, meaning they have theoretically infinite steps.
+They can provide streams of numbers that can be sampled at discrete points in time.
+
+### saw
+
+
+
+### sine
+
+
+
+### cosine
+
+
+
+### tri
+
+
+
+### square
+
+
+
+### Ranges from -1 to 1
+
+There is also `saw2`, `sine2`, `cosine2`, `tri2` and `square2` which have a range from -1 to 1!
+
+### rand
+
+
+
+### perlin
+
+
+
+### irand
+
+
+
+## Random Modifiers
+
+These methods add random behavior to your Patterns.
+
+### chooseCycles
+
+
+
+### Pattern.degradeBy
+
+
+
+### Pattern.degrade
+
+
+
+### Pattern.undegradeBy
+
+
+
+### Pattern.sometimesBy
+
+
+
+### Pattern.sometimes
+
+
+
+### Pattern.someCyclesBy
+
+
+
+### Pattern.someCycles
+
+
+
+### Pattern.often
+
+
+
+### Pattern.rarely
+
+
+
+### Pattern.almostNever
+
+
+
+### Pattern.almostAlways
+
+
+
+### Pattern.never
+
+
+
+### Pattern.always
+
+
+
+
+
+
+# Tonal API
+
+The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations.
+
+### transpose(semitones)
+
+Transposes all notes to the given number of semitones:
+
+".slow(2)).note()`} />
+
+This method gets really exciting when we use it with a pattern as above.
+
+Instead of numbers, scientific interval notation can be used as well:
+
+".slow(2)).note()`} />
+
+### scale(name)
+
+Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranpose.
+
+
+
+Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3.
+
+All the available scale names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).
+
+### scaleTranspose(steps)
+
+Transposes notes inside the scale by the number of steps:
+
+")
+.note()`}
+/>
+
+### voicings(range?)
+
+Turns chord symbols into voicings, using the smoothest voice leading possible:
+
+".voicings('lefthand'), "").note()`} />
+
+### rootNotes(octave = 2)
+
+Turns chord symbols into root notes of chords in given octave.
+
+".rootNotes(3).note()`} />
+
+Together with layer, struct and voicings, this can be used to create a basic backing track:
+
+".layer(
+ x => x.voicings('lefthand').struct("~ x").note(),
+ x => x.rootNotes(2).note().s('sawtooth').cutoff(800)
+)`}
+/>
+
+
+
+
+
+# MIDI API
+
+Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi).
+
+### midi(outputName?)
+
+Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages.
+If no outputName is given, it uses the first midi output it finds.
+
+".voicings('lefthand'), "")
+ .midi()`}
+/>
+
+In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
+
+# Superdirt API
+
+In mainline tidal, the actual sound is generated via Superdirt, which runs inside Supercollider.
+Strudel also supports using Superdirt as a backend, although it requires some developer tooling to run.
+
+## Prequisites
+
+Getting Superdirt to work with Strudel, you need to
+
+1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info.
+2. install [node.js](https://nodejs.org/en/)
+3. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed)
+4. run `npm i` in the strudel directory
+5. run `npm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider
+
+Now you're all set!
+
+## Usage
+
+1. Start SuperCollider, either using SuperCollider IDE or by running `sclang` in a terminal
+2. Open the [Strudel REPL](https://strudel.tidalcycles.org/#cygiYmQgc2QiKS5vc2MoKQ%3D%3D)
+
+...or test it here:
+
+
+
+If you now hear sound, congratulations! If not, you can get help on the [#strudel channel in the TidalCycles discord](https://discord.com/invite/HGEdXmRkzT).
+
+### Pattern.osc
+
+
+
+## Superdirt Params
+
+The following functions can be used with superdirt:
+
+`s n note freq channel orbit cutoff resonance hcutoff hresonance bandf bandq djf vowel cut begin end loop fadeTime speed unitA gain amp accelerate crush coarse delay lock leslie lrate lsize pan panspan pansplay room size dry shape squiz waveloss attack decay octave detune tremolodepth`
+
+Please refer to [Tidal Docs](https://tidalcycles.org/) for more info.
diff --git a/website/src/config.ts b/website/src/config.ts
new file mode 100644
index 00000000..25509d7b
--- /dev/null
+++ b/website/src/config.ts
@@ -0,0 +1,55 @@
+export const SITE = {
+ title: 'Strudel Docs',
+ description: 'Documentation for the Strudel Live Coding Language',
+ defaultLanguage: 'en_US',
+};
+
+export const OPEN_GRAPH = {
+ image: {
+ src: 'https://github.com/withastro/astro/blob/main/assets/social/banner-minimal.png?raw=true',
+ alt:
+ 'astro logo on a starry expanse of space,' +
+ ' with a purple saturn-like planet floating in the right foreground',
+ },
+ twitter: 'astrodotbuild',
+};
+
+// This is the type of the frontmatter you put in the docs markdown files.
+export type Frontmatter = {
+ title: string;
+ description: string;
+ layout: string;
+ image?: { src: string; alt: string };
+ dir?: 'ltr' | 'rtl';
+ ogLocale?: string;
+ lang?: string;
+};
+
+export const KNOWN_LANGUAGES = {
+ English: 'en',
+} as const;
+export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
+
+export const GITHUB_EDIT_URL = `https://github.com/withastro/astro/tree/main/examples/docs`;
+
+export const COMMUNITY_INVITE_URL = `https://astro.build/chat`;
+
+// See "Algolia" section of the README for more information.
+export const ALGOLIA = {
+ indexName: 'XXXXXXXXXX',
+ appId: 'XXXXXXXXXX',
+ apiKey: 'XXXXXXXXXX',
+};
+
+export type Sidebar = Record<
+ typeof KNOWN_LANGUAGE_CODES[number],
+ Record
+>;
+export const SIDEBAR: Sidebar = {
+ en: {
+ 'Learn': [
+ { text: 'Tutorial', link: 'tutorial' },
+ ],
+ // 'Another Section': [{ text: 'Page 4', link: 'en/page-4' }],
+ },
+};
diff --git a/website/src/env.d.ts b/website/src/env.d.ts
new file mode 100644
index 00000000..f964fe0c
--- /dev/null
+++ b/website/src/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/website/src/languages.ts b/website/src/languages.ts
new file mode 100644
index 00000000..405b6921
--- /dev/null
+++ b/website/src/languages.ts
@@ -0,0 +1,10 @@
+import { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES } from './config';
+export { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES };
+
+export const langPathRegex = /\/([a-z]{2}-?[A-Z]{0,2})\//;
+
+export function getLanguageFromURL(pathname: string) {
+ const langCodeMatch = pathname.match(langPathRegex);
+ const langCode = langCodeMatch ? langCodeMatch[1] : 'en';
+ return langCode as typeof KNOWN_LANGUAGE_CODES[number];
+}
diff --git a/website/src/layouts/DefaultLayout.astro b/website/src/layouts/DefaultLayout.astro
new file mode 100644
index 00000000..4d318921
--- /dev/null
+++ b/website/src/layouts/DefaultLayout.astro
@@ -0,0 +1,136 @@
+---
+import HeadCommon from '../components/HeadCommon.astro';
+import HeadSEO from '../components/HeadSEO.astro';
+import Header from '../components/Header/Header.astro';
+import PageContent from '../components/PageContent/PageContent.astro';
+import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
+import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
+import * as CONFIG from '../config';
+import type { MarkdownHeading } from 'astro';
+import Footer from '../components/Footer/Footer.astro';
+
+type Props = {
+ frontmatter: CONFIG.Frontmatter;
+ headings: MarkdownHeading[];
+};
+
+const { frontmatter, headings } = Astro.props as Props;
+const canonicalURL = new URL(Astro.url.pathname, Astro.site);
+const currentPage = Astro.url.pathname;
+const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.md`;
+const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
+---
+
+
+
+
+
+
+ {frontmatter.title ? `${frontmatter.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/src/layouts/MainLayout.astro b/website/src/layouts/MainLayout.astro
new file mode 100644
index 00000000..e25f1a8e
--- /dev/null
+++ b/website/src/layouts/MainLayout.astro
@@ -0,0 +1,51 @@
+---
+import HeadCommon from '../components/HeadCommon.astro';
+import HeadSEO from '../components/HeadSEO.astro';
+import Header from '../components/Header/Header.astro';
+import PageContent from '../components/PageContent/PageContent.astro';
+import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
+import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
+import * as CONFIG from '../config';
+import type { MarkdownHeading } from 'astro';
+import Footer from '../components/Footer/Footer.astro';
+
+type Props = {
+ frontmatter: CONFIG.Frontmatter;
+ headings: MarkdownHeading[];
+};
+
+const { frontmatter, headings } = Astro.props as Props;
+const canonicalURL = new URL(Astro.url.pathname, Astro.site);
+const currentPage = Astro.url.pathname;
+const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.md`;
+const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
+---
+
+
+
+
+
+
+ {frontmatter.title ? `${frontmatter.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title}
+
+
+
+
+