Init
This commit is contained in:
29
src/assets/links.toml
Normal file
29
src/assets/links.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[[links]]
|
||||
id = "jupyterhub"
|
||||
icon = "https://jupyter.org/assets/homepage/main-logo.svg"
|
||||
|
||||
[links.config]
|
||||
title = "Jupyter Hub"
|
||||
link = "`${window.location.origin}/jupyter`"
|
||||
description = "The main System build on top of Docker"
|
||||
tags = [ "Teaching", "Docker" ]
|
||||
|
||||
[[links]]
|
||||
id = "ifnwebsite"
|
||||
icon = "https://www.tu-braunschweig.de/fileadmin/Logos_Einrichtungen/Institute_FK5/logo_IFN.svg"
|
||||
|
||||
[links.config]
|
||||
title = "IFN Website"
|
||||
link = "https://www.tu-braunschweig.de/ifn"
|
||||
description = "All about the Institut"
|
||||
tags = [ "Info" ]
|
||||
|
||||
[[links]]
|
||||
id = "course-prog"
|
||||
icon = "https://www.tu-braunschweig.de/fileadmin/Logos_Einrichtungen/Institute_FK5/logo_IFN.svg"
|
||||
|
||||
[links.config]
|
||||
title = "Course Site"
|
||||
link = "https://www.tu-braunschweig.de/ifn/edu/ws/einfuehrung-in-die-programmierung-fuer-nicht-informatiker"
|
||||
description = "Stuff about Einführung in die Programmierung für NichtInformatiker*innen"
|
||||
tags = [ "Info", "Teaching" ]
|
5
src/components/Cell/Cell.jsx
Normal file
5
src/components/Cell/Cell.jsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import './style.css';
|
||||
|
||||
export default function Cell({ children }) {
|
||||
return <div class="cell">{children}</div>
|
||||
}
|
17
src/components/Cell/CellInput.jsx
Normal file
17
src/components/Cell/CellInput.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import Highlight from 'react-highlight';
|
||||
import './jupyter-theme.css';
|
||||
|
||||
export default function CellInput({ children }) {
|
||||
const randomTurn = Math.floor(Math.random() * 111) + 1;
|
||||
|
||||
return (
|
||||
<div class="cell-input">
|
||||
<div class="cell-turn">In [{randomTurn}]:</div>
|
||||
<div class="cell-content">
|
||||
<Highlight className='python'>
|
||||
{children}
|
||||
</Highlight>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
12
src/components/Cell/CellOutput.jsx
Normal file
12
src/components/Cell/CellOutput.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function CellOutput({ children }) {
|
||||
const randomTurn = Math.floor(Math.random() * 111) + 1;
|
||||
|
||||
return (
|
||||
<div class="cell-output">
|
||||
<div class="cell-turn">Out [{randomTurn}]:</div>
|
||||
<div class="cell-content-2">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
38
src/components/Cell/jupyter-theme.css
Normal file
38
src/components/Cell/jupyter-theme.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #F5F5F5;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
.hljs-subst {
|
||||
color: #434f54;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-keyword,
|
||||
.hljs-built_in {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.hljs-keyword {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-comment {
|
||||
color: #408080;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-string {
|
||||
color: #BA2121;
|
||||
}
|
||||
|
||||
.hljs-operator {
|
||||
color: #7800C2;
|
||||
}
|
||||
|
||||
.hljs-title {
|
||||
color: red;
|
||||
}
|
57
src/components/Cell/style.css
Normal file
57
src/components/Cell/style.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.cell {
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.cell-input {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 15px 20px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 13px;
|
||||
color: #24292e;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.cell-content {
|
||||
flex: 0 0 90%;
|
||||
border: 1px solid #b0b0b0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cell-content-2 {
|
||||
flex: 0 0 90%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cell-turn {
|
||||
flex: 1;
|
||||
color: #b0b0b0;
|
||||
margin-top: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cell-output {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 15px 20px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 13px;
|
||||
color: #24292e;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.cell-input {
|
||||
font-size: 12px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.cell-output {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
5
src/components/KernelStatus/KernelStatus.jsx
Normal file
5
src/components/KernelStatus/KernelStatus.jsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import './style.css';
|
||||
|
||||
export default function KernelStatus() {
|
||||
return <span class="kernel-status"></span>;
|
||||
}
|
21
src/components/KernelStatus/style.css
Normal file
21
src/components/KernelStatus/style.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.kernel-status {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #28a745;
|
||||
margin-right: 8px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
53
src/components/Links/Links.jsx
Normal file
53
src/components/Links/Links.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import './style.css';
|
||||
|
||||
const Tag = ({ tag }) => {
|
||||
console.log(tag)
|
||||
return (
|
||||
<div class="link-tag">
|
||||
{tag}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const LinkItem = ({ linkData }) => {
|
||||
const { config, icon } = linkData;
|
||||
const fullUrl = `${window.location.origin}${config.link}`;
|
||||
console.log(config.tags)
|
||||
return (
|
||||
<a
|
||||
href={config.link}
|
||||
class="link-item"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src={icon}
|
||||
class="link-icon"
|
||||
onError={(e) => {
|
||||
// Fallback to a default icon if the image fails to load
|
||||
e.target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzY2NjY2NiI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAgNDg5IDEwIDEwUzE3LjUyIDIgMTIgMnptLTIgMTVsLTUtNSAxLjQxLTEuNDFMMTAuNTcgMTVsMi4yNDktMi4yNDkgMS40MTQgMS40MTQtMy4yNDkgMy4yNDlMMTAgMTd6Ii8+PC9zdmc+';
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="link-content">
|
||||
<div class="link-title">{config.title}</div>
|
||||
<div class="link-description">{config.description}</div>
|
||||
<div class="link-tags">
|
||||
{config.tags.map(tag => (<p class="link-tag">{tag}</p>))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="link-url">
|
||||
{config.link}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Links({ links }) {
|
||||
return (
|
||||
<div class="links-container">
|
||||
{links.map(link => (<LinkItem key={link.id} linkData={link} />))}
|
||||
</div>
|
||||
);
|
||||
}
|
96
src/components/Links/style.css
Normal file
96
src/components/Links/style.css
Normal file
@@ -0,0 +1,96 @@
|
||||
.links-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.link-item {
|
||||
width: 95%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border: 1px solid #d1d5da;
|
||||
border-radius: 6px;
|
||||
background: white;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.link-item:hover {
|
||||
border-color: #0366d6;
|
||||
box-shadow: 0 3px 8px rgba(3, 102, 214, 0.2);
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
width: auto;
|
||||
height: 48px;
|
||||
margin-right: 20px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.link-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.link-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #24292e;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.link-description {
|
||||
color: #586069;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.link-tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.link-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
background-color: #586069;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.link-url {
|
||||
display: inline-block;
|
||||
max-width: 30%;
|
||||
white-space: collapse;
|
||||
color: #586069;
|
||||
font-size: 0.8rem;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
background: #f6f8fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
margin-left: auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.link-item {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.link-url {
|
||||
margin-left: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
10
src/components/Loading/Loading.jsx
Normal file
10
src/components/Loading/Loading.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import './style.css';
|
||||
import KernelStatus from '../KernelStatus/KernelStatus.jsx';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div class="loading">
|
||||
<KernelStatus />Loading links...
|
||||
</div>
|
||||
);
|
||||
}
|
9
src/components/Loading/style.css
Normal file
9
src/components/Loading/style.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #586069;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
126
src/components/StatsChart/StatsChart.jsx
Normal file
126
src/components/StatsChart/StatsChart.jsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import './style.css';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
} from 'chart.js';
|
||||
import { Chart } from 'react-chartjs-2';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
function toCleanTitleCase(str) {
|
||||
return str
|
||||
.replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces
|
||||
.replace(
|
||||
/\w\S*/g,
|
||||
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
const chartAreaBorder = {
|
||||
id: 'chartAreaBorder',
|
||||
beforeDraw(chart, args, options) {
|
||||
const {ctx, chartArea: {left, top, width, height}} = chart;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = options.borderColor;
|
||||
ctx.lineWidth = options.borderWidth;
|
||||
ctx.setLineDash(options.borderDash || []);
|
||||
ctx.lineDashOffset = options.borderDashOffset;
|
||||
ctx.strokeRect(left, top, width, height);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
export default function StatsChart({ links }) {
|
||||
const options = {
|
||||
responsive: true,
|
||||
animation: false,
|
||||
interaction: {
|
||||
mode: null,
|
||||
intersect: false,
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: false
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Stats',
|
||||
font: {
|
||||
family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
|
||||
weight: 'bold',
|
||||
size: 17,
|
||||
color: 'black',
|
||||
lineHeight: 1.2,
|
||||
}
|
||||
},
|
||||
chartAreaBorder: {
|
||||
borderColor: 'black',
|
||||
borderWidth: 1,
|
||||
borderDash: [0, 0],
|
||||
borderDashOffset: 0,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
border: { display: false },
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
color: 'black',
|
||||
font: { size: 15 }
|
||||
}
|
||||
},
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: { display: false},
|
||||
ticks: {
|
||||
color: 'black',
|
||||
font: { size: 15 }
|
||||
}
|
||||
}
|
||||
},
|
||||
maintainAspectRatio: true,
|
||||
aspectRatio: 1 | 1,
|
||||
}
|
||||
|
||||
const labels = [
|
||||
"Overall",
|
||||
...links.map(item => toCleanTitleCase(item.id))
|
||||
];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Stats",
|
||||
data: [
|
||||
links.length,
|
||||
...Array.from(
|
||||
{ length: labels.length },
|
||||
() => Math.floor(Math.random() * (links.length -1)) + 1
|
||||
)],
|
||||
backgroundColor: ['#1F77B4', '#FF7F0E', '#2CA02C', '#D62728', '#9467BD'],
|
||||
}
|
||||
],
|
||||
}
|
||||
return (
|
||||
<div class="chart-container">
|
||||
<Chart type="bar" options={options} data={data} plugins={[chartAreaBorder]} />
|
||||
</div>
|
||||
);
|
||||
}
|
6
src/components/StatsChart/style.css
Normal file
6
src/components/StatsChart/style.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 30rem;
|
||||
width: 30rem;
|
||||
background-color: transparent;
|
||||
}
|
10
src/components/StatusBar/StatusBar.jsx
Normal file
10
src/components/StatusBar/StatusBar.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import './style.css';
|
||||
import KernelStatus from '../KernelStatus/KernelStatus.jsx';
|
||||
|
||||
export default function StatusBar({ links }) {
|
||||
return (
|
||||
<div class="status-bar">
|
||||
<KernelStatus />Kernel ready • {links.length} links loaded • Ready for interaction
|
||||
</div>
|
||||
);
|
||||
}
|
11
src/components/StatusBar/style.css
Normal file
11
src/components/StatusBar/style.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.status-bar {
|
||||
font-size: 0.85rem;
|
||||
color: #586069;
|
||||
padding: 10px 0;
|
||||
border-top: 1px solid #e1e4e8;
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
33
src/index.jsx
Normal file
33
src/index.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { LocationProvider, Router, Route, hydrate, prerender as ssr } from 'preact-iso';
|
||||
import './style.css';
|
||||
import Home from './pages/Home/index.jsx';
|
||||
import NotFound from './pages/_404.jsx';
|
||||
|
||||
export function App() {
|
||||
const routes = [
|
||||
{ desc: "Home", path: "/", component: Home },
|
||||
]
|
||||
|
||||
const links = routes.map(route => ({desc: route.desc, path: route.path}))
|
||||
|
||||
return (
|
||||
<LocationProvider>
|
||||
<main>
|
||||
<Router>
|
||||
{routes.map(route => (
|
||||
<Route path={route.path} component={route.component} />
|
||||
))}
|
||||
<Route default component={NotFound} />
|
||||
</Router>
|
||||
</main>
|
||||
</LocationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
hydrate(<App />, document.getElementById('app'));
|
||||
}
|
||||
|
||||
export async function prerender(data) {
|
||||
return await ssr(<App {...data} />);
|
||||
}
|
124
src/pages/Home/index.jsx
Normal file
124
src/pages/Home/index.jsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
import './style.css';
|
||||
|
||||
// Components
|
||||
|
||||
import Cell from '../../components/Cell/Cell.jsx';
|
||||
import CellInput from '../../components/Cell/CellInput.jsx';
|
||||
import CellOutput from '../../components/Cell/CellOutput.jsx';
|
||||
|
||||
import StatsChart from '../../components/StatsChart/StatsChart.jsx';
|
||||
|
||||
import StatusBar from '../../components/StatusBar/StatusBar.jsx';
|
||||
import Loading from '../../components/Loading/Loading.jsx';
|
||||
import Links from '../../components/Links/Links.jsx';
|
||||
|
||||
const linksCode = `# Loading Dataframe
|
||||
from utils import link_container
|
||||
import pandas as pd
|
||||
|
||||
links = pd.read_csv('ifn_links.csv')
|
||||
links.drop_duplicates()
|
||||
for index, link in links.iterrows():
|
||||
link_container.attach(index, link)
|
||||
link_container.show()
|
||||
`
|
||||
|
||||
const statsCode = `# Plotting Statistics
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
plt.title("Stats")
|
||||
plt.bar("Links Loaded", len(links))
|
||||
for link in links:
|
||||
plt.bar(link, np.random.randint(1, len(links) - 1))
|
||||
plt.show()
|
||||
`
|
||||
|
||||
const getMockLinks = () => {
|
||||
return [
|
||||
{
|
||||
id: 'jupyterhub',
|
||||
config: {
|
||||
title: 'Jupyter Hub',
|
||||
link: `${window.location.origin}/jupyter`,
|
||||
description: 'The main System build on top of Docker',
|
||||
tags: ['Teaching', 'Docker']
|
||||
},
|
||||
icon: 'https://jupyter.org/assets/homepage/main-logo.svg'
|
||||
},
|
||||
{
|
||||
id: 'ifnwebsite',
|
||||
config: {
|
||||
title: 'IFN Website',
|
||||
link: 'https://www.tu-braunschweig.de/ifn',
|
||||
description: 'All about the institut',
|
||||
tags: ['Info']
|
||||
},
|
||||
icon: 'https://www.tu-braunschweig.de/fileadmin/Logos_Einrichtungen/Institute_FK5/logo_IFN.svg'
|
||||
},
|
||||
{
|
||||
id: 'course-prog',
|
||||
config: {
|
||||
title: 'Lehrseite',
|
||||
link: 'https://www.tu-braunschweig.de/ifn/edu/ws/einfuehrung-in-die-programmierung-fuer-nicht-informatiker',
|
||||
description: 'Useful Stuff about the course',
|
||||
tags: ['Info', 'Teaching']
|
||||
},
|
||||
icon: 'https://www.tu-braunschweig.de/fileadmin/Logos_Einrichtungen/Institute_FK5/logo_IFN.svg'
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default function Home() {
|
||||
const [links, setLinks] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = 500 + Math.random() * 1500;
|
||||
const timer = setTimeout(() => {
|
||||
setLinks(getMockLinks())
|
||||
setLoading(false);
|
||||
}, delay)
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div class="notebook-container">
|
||||
<div class="notebook-header">
|
||||
<div class="header-container">
|
||||
<h1 class="notebook-title">Teaching System</h1>
|
||||
<img class="notebook-img" src="https://www.tu-braunschweig.de/fileadmin/Logos_Einrichtungen/Institute_FK5/logo_IFN.svg" />
|
||||
</div>
|
||||
<div class="notebook-subtitle">
|
||||
A linktree of all available systems
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Cell>
|
||||
<CellInput>
|
||||
{linksCode}
|
||||
</CellInput>
|
||||
|
||||
<CellOutput>
|
||||
{loading ? (<Loading />) : (<Links links={links} />)}
|
||||
</CellOutput>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<CellInput>
|
||||
{statsCode}
|
||||
</CellInput>
|
||||
<CellOutput>
|
||||
<StatsChart links={links}/>
|
||||
</CellOutput>
|
||||
</Cell>
|
||||
|
||||
<StatusBar links={links}/>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
54
src/pages/Home/style.css
Normal file
54
src/pages/Home/style.css
Normal file
@@ -0,0 +1,54 @@
|
||||
.notebook-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
box-shadow: 0 0 12px rgba(87, 87, 87, 0.2);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.notebook-header {
|
||||
border-bottom: 1px solid #e1e4e8;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notebook-image {
|
||||
height: 1000px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.notebook-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 400;
|
||||
color: #24292e;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.notebook-subtitle {
|
||||
color: #586069;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 300;
|
||||
margin-top: 0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.notebook-container {
|
||||
padding: 15px;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.notebook-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
5
src/pages/_404.jsx
Normal file
5
src/pages/_404.jsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<h1>404</h1>
|
||||
);
|
||||
}
|
15
src/style.css
Normal file
15
src/style.css
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Jupyter Notebook inspired styling */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
color: black;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user