+ {flatNodes.map(node => (
+
+
{ICONS[node.type]}
+
{node.name}
+
+
+ ))}
+
+ )
+}
diff --git a/src/client/fonts.css b/src/client/fonts.css
new file mode 100644
index 0000000..9d2886f
--- /dev/null
+++ b/src/client/fonts.css
@@ -0,0 +1,47 @@
+/* fira-mono-latin-400-normal */
+@font-face {
+ font-family: 'Fira Mono';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 400;
+ src: url(https://cdn.jsdelivr.net/fontsource/fonts/fira-mono@latest/latin-400-normal.woff2) format('woff2'),
+ url(https://cdn.jsdelivr.net/fontsource/fonts/fira-mono@latest/latin-400-normal.woff) format('woff');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
+ U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* fira-mono-latin-500-normal */
+@font-face {
+ font-family: 'Fira Mono';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 500;
+ src: url(https://cdn.jsdelivr.net/fontsource/fonts/fira-mono@latest/latin-500-normal.woff2) format('woff2'),
+ url(https://cdn.jsdelivr.net/fontsource/fonts/fira-mono@latest/latin-500-normal.woff) format('woff');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
+ U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* fira-mono-latin-700-normal */
+@font-face {
+ font-family: 'Fira Mono';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 700;
+ src: url(https://cdn.jsdelivr.net/fontsource/fonts/fira-mono@latest/latin-700-normal.woff2) format('woff2'),
+ url(https://cdn.jsdelivr.net/fontsource/fonts/fira-mono@latest/latin-700-normal.woff) format('woff');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
+ U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* inter-latin-wght-normal */
+@font-face {
+ font-family: 'Inter Variable';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 100 900;
+ src: url(https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-opsz-italic.woff2)
+ format('woff2-variations');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
+ U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
diff --git a/src/client/style.css b/src/client/style.css
index 7ebf586..bd6f20f 100644
--- a/src/client/style.css
+++ b/src/client/style.css
@@ -24,8 +24,39 @@ body {
-moz-osx-font-smoothing: grayscale;
}
+/* Utility */
+
+.flex-row {
+ display: flex;
+ flex-direction: row;
+
+ .flex-grow {
+ flex-grow: 1;
+ }
+}
+
/* Components */
+.material-symbols-outlined {
+ font-family: 'Material Symbols Outlined Variable';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 20px; /* Preferred icon size */
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+
+ display: grid;
+ place-content: center;
+}
+
+.material-symbols-outlined {
+ font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 20;
+}
+
.xterm {
margin: 2rem;
padding: 0.5rem;
@@ -38,3 +69,462 @@ body {
font-family: 'JetBrains Mono', monospace;
}
}
+
+textarea.code-editor-prototype {
+ appearance: none;
+ resize: none;
+ outline: none;
+ border: none;
+ background: #fff;
+
+ width: 100%;
+ height: 100%;
+
+ font-family: 'Fira Code Variable', monospace;
+ font-weight: 400;
+ font-size: 15px;
+}
+
+input[type='text'] {
+ appearance: none;
+ outline: none;
+ border: none;
+ background: #fff;
+
+ font-size: 16px;
+
+ border: 1px solid #d9d9d9;
+ background: #fff;
+
+ padding: 0.25rem 0.5rem;
+
+ border-radius: 0.25rem;
+
+ box-shadow: 0 0.125rem 0.125rem 0 #00000008;
+
+ min-height: 1.75rem;
+}
+
+button,
+input[type='submit'],
+[role='button'] {
+ appearance: none;
+ outline: none;
+ border: 1px solid #d9d9d9;
+ background: #fff;
+
+ display: grid;
+ place-content: center;
+ place-items: center;
+ grid-auto-flow: column;
+
+ font-size: 15px;
+ font-weight: 500;
+
+ padding: 0.25rem 0.5rem;
+ gap: 0.25rem;
+
+ border-radius: 0.25rem;
+
+ box-shadow: 0 0.125rem 0.125rem 0 #00000008;
+
+ min-height: 1.75rem;
+
+ cursor: pointer;
+
+ &:not(.icon):has(.material-symbols-outlined:first-child) {
+ padding-left: 0.125rem;
+ }
+
+ &:hover {
+ background: #f8f8f8;
+ }
+
+ &.icon {
+ padding: 0.25rem;
+ aspect-ratio: 1 / 1;
+ }
+
+ &.run {
+ background: hsl(120, 35%, 46%);
+ border-color: hsl(120, 38%, 41%);
+ color: #fff;
+
+ .material-symbols-outlined {
+ font-variation-settings: 'FILL' 1, 'wght' 300, 'GRAD' 0, 'opsz' 20;
+ }
+
+ box-shadow: 0 0.125rem 0.2rem 0 hsla(120, 40%, 53%, 0.37);
+
+ &:hover {
+ background: hsl(120, 35%, 51%);
+ }
+ }
+
+ &.flat {
+ min-height: auto;
+ border: none;
+ background: none;
+ box-shadow: none;
+
+ &.icon {
+ padding: 0;
+ }
+
+ &:hover {
+ background: #0002;
+ }
+ }
+}
+
+.user-details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ grid-template-rows: 1fr 1fr;
+
+ padding: 0.5rem;
+ column-gap: 0.5rem;
+
+ border: 1px solid #d9d9d9;
+ border-radius: 0.5rem;
+ box-shadow: 0 0.125rem 0.125rem 0 #00000008;
+
+ > .profile {
+ grid-column: 1 / 2;
+ grid-row: 1 / 3;
+
+ img {
+ width: 2.5rem;
+ height: 2.5rem;
+ object-fit: cover;
+ }
+ }
+
+ > .username {
+ grid-column: 2 / 3;
+ grid-row: 1 / 2;
+
+ font-size: 16px;
+ font-weight: 500;
+
+ align-self: end;
+ }
+
+ > .email {
+ grid-column: 2 / 3;
+ grid-row: 2 / 3;
+
+ align-self: start;
+
+ font-size: 14px;
+ color: #777;
+ }
+
+ cursor: pointer;
+
+ &:hover {
+ background: #00000004;
+ }
+}
+
+.project-details {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-content: center;
+
+ padding: 0 0.25rem 0 0.5rem;
+ column-gap: 0.5rem;
+
+ min-height: 2rem;
+
+ border: 1px solid #d9d9d9;
+ border-radius: 0.35rem;
+ box-shadow: 0 0.125rem 0.125rem 0 #00000008;
+
+ font-size: 15px;
+ font-weight: 450;
+
+ cursor: pointer;
+
+ &:hover {
+ background: #00000004;
+ }
+}
+
+.tree-view {
+ display: grid;
+ grid-auto-flow: row;
+ gap: 0.25rem;
+
+ > .entry {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+
+ padding: 0.125rem 0.5rem 0.125rem 0.25rem;
+ border-radius: 0.35rem;
+ gap: 0.35rem;
+
+ margin-left: calc(var(--depth) * 1rem);
+
+ cursor: pointer;
+
+ &:hover {
+ background: #00000006;
+ }
+ }
+}
+
+.ide {
+ user-select: none;
+
+ display: grid;
+ height: 100%;
+
+ grid-template-columns: 20rem 1fr;
+ grid-template-rows: 3rem 1fr 33vh auto;
+
+ grid-template-areas:
+ 'sidebar header'
+ 'sidebar editor'
+ 'sidebar terminal'
+ 'sidebar status';
+
+ > .sidebar {
+ grid-area: sidebar;
+
+ display: flex;
+ flex-direction: column;
+
+ padding: 1rem;
+ gap: 1rem;
+
+ border-right: 1px solid #d9d9d9;
+
+ .flex-row {
+ gap: 0.5rem;
+ }
+
+ > * {
+ flex-shrink: 0;
+ }
+
+ .logo {
+ font-size: 24px;
+ font-weight: 700;
+ }
+
+ > .spacer {
+ flex-grow: 1;
+ }
+
+ > .section {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ /* border-radius: 0.35rem;
+
+ padding: 0.25rem;
+
+ &:hover {
+ background: #00000004;
+ } */
+
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #d9d9d9;
+
+ > .spoiler {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ align-content: center;
+
+ font-weight: 550;
+
+ border-radius: 0.35rem;
+ padding: 0.35rem;
+ gap: 0.5rem;
+
+ cursor: pointer;
+
+ &:hover {
+ background: #00000006;
+ }
+ }
+ }
+ }
+
+ > .header {
+ grid-area: header;
+
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+
+ gap: 1rem;
+
+ padding: 0 0.5rem;
+
+ border-bottom: 1px solid #d9d9d9;
+
+ > .search {
+ place-self: center;
+
+ display: grid;
+
+ width: 100%;
+ max-width: 40rem;
+
+ position: relative;
+
+ > .material-symbols-outlined {
+ position: absolute;
+
+ right: 0;
+
+ top: 50%;
+ transform: translateY(-50%);
+
+ padding-right: 0.35rem;
+ }
+ }
+
+ > .actions {
+ display: grid;
+ grid-auto-flow: column;
+ align-items: center;
+
+ gap: 0.5rem;
+ }
+ }
+
+ > .tabbed-editor {
+ grid-area: editor;
+
+ display: grid;
+ grid-template-rows: auto 1fr;
+
+ /* background: #fafcfe; */
+ background: #f5efff;
+
+ > .tabs {
+ display: grid;
+ grid-auto-flow: column;
+
+ padding: 0.5rem;
+ padding-bottom: 0;
+
+ justify-content: start;
+
+ border-bottom: 1px solid #d9d9d9;
+
+ > .tab {
+ background: #fff;
+
+ border: 1px solid #d9d9d9;
+ border-bottom: none;
+ border-top-left-radius: 0.5rem;
+ border-top-right-radius: 0.5rem;
+
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 0.5rem;
+
+ align-items: center;
+
+ padding: 0.35rem;
+
+ font-size: 15px;
+ font-weight: 450;
+
+ z-index: 1;
+ transform: translate(0, 1px);
+
+ cursor: pointer;
+
+ > .title {
+ padding: 0 0 0 0.15rem;
+ }
+
+ &:not(.active) {
+ border-bottom: 1px solid #d9d9d9;
+
+ &:hover {
+ background: #f8f8f8;
+ }
+ }
+
+ &:not(:first-child) {
+ border-left: none;
+ }
+ }
+ }
+
+ > .editor {
+ background: #fff;
+ padding: 0.5rem;
+ }
+ }
+
+ > .terminal {
+ grid-area: terminal;
+ position: relative;
+
+ border-top: 1px solid #d9d9d9;
+
+ display: grid;
+ grid-template-rows: auto 1fr;
+
+ padding: 0.5rem;
+ gap: 0.5rem;
+
+ > .actions {
+ position: absolute;
+ top: 0;
+ right: 0;
+
+ display: grid;
+ grid-auto-flow: column;
+ gap: 0.25rem;
+
+ padding: 0.5rem;
+ }
+
+ &:not(:hover) > .actions {
+ .material-symbols-outlined {
+ color: #888;
+ }
+ }
+
+ > .title {
+ font-size: 14px;
+ font-weight: 550;
+ }
+
+ > .content {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 14px;
+ }
+ }
+
+ > .status {
+ border-top: 1px solid #d9d9d9;
+
+ padding: 0.125rem 0.25rem;
+
+ display: flex;
+ flex-direction: row;
+ gap: 0.25rem;
+
+ font-size: 13px;
+ }
+}
+
+/* Layout */
+
+body {
+ font-family: 'Inter Variable', sans-serif;
+ font-size: 16px;
+ font-weight: 400;
+
+ color: #333;
+}
diff --git a/src/components/Ide.astro b/src/components/Ide.astro
new file mode 100644
index 0000000..377b776
--- /dev/null
+++ b/src/components/Ide.astro
@@ -0,0 +1,116 @@
+---
+import { TreeView, EXAMPLE_TREE } from '@/client/components/TreeView.jsx'
+import { Editor } from '@/client/components/Editor.jsx'
+
+const { project } = Astro.props
+
+const userName = project.split('/').at(0).slice(1)
+const projectName = project.split('/').at(-1)
+---
+
+