| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- ---
- import scriptIcon from "../assets/script.png";
- import Win98Window from "./win98window.astro"
- let projects = Astro.props.projects;
- ---
- <Win98Window
- title="Projects "
- layout={{
- mobile: { left: '3%', top: '10%', width: '94%', height: '78vh' },
- desktop: { left: '10%', top: '10%', width: '80%', height: '75vh' }
- }}
- >
- <div id="inner">
- <div class="explorer-toolbar" aria-hidden="true">
- <span class="explorer-path">C:\Users\simon\Projects</span>
- </div>
- <div class="explorer-files">
- <div id="projects-container">
- <div class="projects-grid" >
- {projects.map((item) => (
- <button
- class="project"
- id={`${item.id}item`}
- type="button"
- data-project={item.id}
- aria-label={`Open ${item.title}`}
- >
- <span class="project-icon" aria-hidden="true"><img src={scriptIcon.src} class="w-auto h-8" alt="" /></span>
- <span class="project-title">{item.title}</span>
- <span class="project-meta" aria-hidden="true">Application</span>
- </button>
- ))}
- </div>
- <div class="projects-details" aria-live="polite">
- <div class="details-header">
- <div class="details-title" id="project-details-title">Select a project</div>
- <div class="details-actions">
- <button class="details-btn" id="project-open" type="button" disabled>Open</button>
- <button class="details-btn" id="project-repo" type="button" disabled>Repo</button>
- </div>
- </div>
- <div class="details-body">
- <div class="details-blurb" id="project-details-blurb">Click once to preview. Double-click still opens.</div>
- <div class="details-framewrap" id="project-details-framewrap" hidden>
- <iframe id="project-details-frame" title="Project preview" loading="lazy" referrerpolicy="no-referrer" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </Win98Window>
- <script>
- (function () {
- const root = document.currentScript?.closest('.window') || document;
- const container = root.querySelector('#projects-container');
- if (!container) return;
- const grid = container.querySelector('.projects-grid');
- if (!grid) return;
- const titleEl = container.querySelector('#project-details-title');
- const blurbEl = container.querySelector('#project-details-blurb');
- const frameWrap = container.querySelector('#project-details-framewrap');
- const frame = container.querySelector('#project-details-frame');
- const openBtn = container.querySelector('#project-open');
- const repoBtn = container.querySelector('#project-repo');
- const projects = {
- mikro: {
- title: 'Mikro',
- previewUrl: 'https://mikro.simo.ng/?embed=1',
- repoUrl: 'https://git.simo.ng/simo/Mikro',
- blurb: 'Minimal, opinionated RSS reader.',
- },
- glance: {
- title: 'Glance',
- previewUrl: 'https://glance.simo.ng',
- repoUrl: 'https://git.simo.ng/simo/Glance',
- blurb: 'Idle dashboard optimized for E-Ink displays.',
- },
- mneme: {
- title: 'Mneme',
- previewUrl: null,
- repoUrl: 'https://git.simo.ng/simo/mneme',
- blurb: 'Tiny Spotify microservice.',
- },
- };
- function openRedirect(url) {
- if (!url) return;
- window.open(url, '_blank', 'noopener,noreferrer');
- }
- function setSelected(projectId) {
- const p = projects[projectId];
- if (!p) return;
- container.querySelectorAll('.project').forEach((el) => {
- el.toggleAttribute('data-selected', el.getAttribute('data-project') === projectId);
- });
- if (titleEl) titleEl.textContent = p.title;
- if (blurbEl) blurbEl.textContent = p.blurb || '';
- const openUrl = p.previewUrl;
- if (openBtn) {
- openBtn.disabled = !openUrl;
- openBtn.onclick = () => openRedirect(openUrl);
- }
- if (repoBtn) {
- repoBtn.disabled = !p.repoUrl;
- repoBtn.onclick = () => openRedirect(p.repoUrl);
- }
- if (frameWrap && frame) {
- if (p.previewUrl) {
- frameWrap.hidden = false;
- frame.src = p.previewUrl;
- } else {
- frameWrap.hidden = true;
- frame.removeAttribute('src');
- }
- }
- }
- // Prevent the homepage window-spawning click handler from running.
- container.querySelectorAll('.project').forEach((pbtn) => {
- pbtn.addEventListener('click', (e) => {
- e.stopImmediatePropagation();
- e.stopPropagation();
- e.preventDefault();
- const id = pbtn.getAttribute('data-project');
- if (id) setSelected(id);
- });
- pbtn.addEventListener('dblclick', (e) => {
- e.stopImmediatePropagation();
- e.stopPropagation();
- e.preventDefault();
- const id = pbtn.getAttribute('data-project');
- if (!id) return;
- const p = projects[id];
- if (!p) return;
- openRedirect(p.previewUrl || p.repoUrl);
- });
- });
- // Pick first project by default for quick scanning
- const first = container.querySelector('.project[data-project]');
- if (first) setSelected(first.getAttribute('data-project'));
- })();
- </script>
- <style>
- #inner {
- height: 100%;
- appearance: none;
- background-color: #fff;
- border-radius: 0;
- box-shadow:
- inset -1px -1px #fff,
- inset 1px 1px grey,
- inset -2px -2px #dfdfdf,
- inset 2px 2px #0a0a0a;
- box-sizing: border-box;
- padding: 0;
- overflow: hidden;
- }
- .explorer-toolbar {
- position: sticky;
- top: 0;
- z-index: 1;
- display: flex;
- align-items: center;
- gap: 6px;
- padding: 10px 10px;
- background: #d4d0c8;
- border-bottom: 1px solid #808080;
- box-shadow: inset 0 1px #fff;
- }
- .explorer-path {
- display: block;
- flex: 1;
- padding: 8px 12px;
- font-size: 14px;
- background: #fff;
- box-shadow:
- inset -1px -1px #fff,
- inset 1px 1px grey,
- inset -2px -2px #dfdfdf,
- inset 2px 2px #0a0a0a;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- min-height: 30px;
- }
- /* File area: add a little inner margin like Explorer */
- .explorer-files {
- padding: 6px;
- height: calc(100% - 44px);
- box-sizing: border-box;
- }
- #projects {
- animation: createBox 0.2s;
- }
- @keyframes createBox {
- from {
- transform: scale(0);
- }
- to {
- transform: scale(1);
- }
- }
- .project {
- user-select: none;
- box-sizing: border-box;
- width: 100%;
- max-width: 100%;
- padding: 4px 6px;
- gap: 8px;
- border: 1px solid transparent;
- }
- .project h3 {
- min-width: 0;
- flex: 1;
- }
- .project:focus {
- outline: 1px dotted #000;
- outline-offset: -3px;
- border-color: #000;
- background: #000080;
- color: #fff;
- }
- #projects-container {
- display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
- grid-template-rows: auto 1fr;
- grid-template-areas:
- "status details"
- "grid details";
- gap: 8px;
- min-height: 0;
- height: 100%;
- }
- .projects-grid {
- grid-area: grid;
- min-height: 0;
- }
- .projects-details {
- grid-area: details;
- min-height: 0;
- }
- /* Ensure the scroll area never spills past the inner frame */
- #projects-container {
- padding-right: 6px;
- margin-right: 2px;
- }
- #projects-container .project:hover {
- background: #e6e6e6;
- border-color: #808080;
- }
- .projects-status {
- display: flex;
- align-items: center;
- gap: 6px;
- padding: 6px 8px;
- margin: 6px;
- background: #efefef;
- border: 1px solid #808080;
- box-shadow: inset 0 1px #fff;
- font-size: 12px;
- }
- .projects-grid {
- height: 100%;
- overflow: auto;
- padding: 6px;
- margin: 0 6px 6px;
- background: #fff;
- box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf,
- inset 2px 2px #0a0a0a;
- }
- .projects-grid {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
- .project {
- text-align: left;
- background: transparent;
- }
- .projects-grid .project {
- display: grid;
- grid-template-columns: 34px 1fr auto;
- align-items: center;
- gap: 8px;
- padding: 6px 8px;
- border: 1px solid transparent;
- }
- .project-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
- .project-title {
- font-weight: 700;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .project-meta {
- font-size: 11px;
- color: #222;
- padding: 2px 6px;
- border: 1px solid #808080;
- background: #efefef;
- box-shadow: inset 0 1px #fff;
- white-space: nowrap;
- }
- .projects-grid .project:hover {
- background: #e6e6e6;
- border-color: #808080;
- }
- .projects-grid .project:focus {
- outline: 1px dotted #000;
- outline-offset: -3px;
- border-color: #000;
- background: #000080;
- color: #fff;
- }
- #projects-container {
- display: grid;
- grid-template-columns: 1fr 1.25fr;
- gap: 8px;
- height: calc(100% - 0px);
- }
- .projects-details {
- display: flex;
- flex-direction: column;
- margin: 6px 6px 6px 0;
- background: #fff;
- box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf,
- inset 2px 2px #0a0a0a;
- min-width: 0;
- }
- .details-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- padding: 8px 10px;
- background: #d4d0c8;
- border-bottom: 1px solid #808080;
- box-shadow: inset 0 1px #fff;
- }
- .details-title {
- font-weight: 700;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .details-actions {
- display: flex;
- gap: 6px;
- flex-shrink: 0;
- }
- .details-btn {
- appearance: none;
- font-family: inherit;
- font-size: 12px;
- padding: 2px 10px;
- background: #d4d0c8;
- border: 1px solid #000;
- box-shadow: inset 1px 1px #fff, inset -1px -1px #808080;
- cursor: pointer;
- }
- .details-btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- }
- .details-body {
- height: 100%;
- padding: 10px;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- gap: 10px;
- min-height: 0;
- }
- .details-blurb {
- font-size: 12px;
- line-height: 1.3;
- color: #222;
- border: 1px solid #000;
- background: #ffffe1;
- box-shadow: 2px 2px 0 #000;
- padding: 8px 10px;
- }
- .details-framewrap {
- flex: 1;
- min-height: 0;
- border: 1px solid #000;
- box-shadow: 2px 2px 0 #000;
- background: #fff;
- overflow: hidden;
- }
- #project-details-frame {
- width: 100%;
- height: 100%;
- border: 0;
- background: #fff;
- }
- .projects-grid {
- margin: 6px 0 6px 6px;
- }
- .projects-grid .project[data-selected] {
- outline: 1px dotted #000;
- outline-offset: -3px;
- border-color: #000;
- background: #000080;
- color: #fff;
- }
- @media (max-width: 510px) {
- #projects-container {
- grid-template-columns: 1fr;
- grid-template-rows: auto 1fr;
- grid-template-areas:
- "grid"
- "details";
- height: 100%;
- }
- .projects-grid {
- height: auto;
- margin: 6px;
- padding: 8px;
- overflow-x: auto;
- overflow-y: hidden;
- display: flex;
- flex-direction: row;
- gap: 8px;
- scroll-snap-type: x mandatory;
- -webkit-overflow-scrolling: touch;
- }
- .projects-grid .project {
- flex: 0 0 auto;
- min-width: 170px;
- max-width: 220px;
- grid-template-columns: 34px 1fr;
- grid-template-rows: auto auto;
- align-items: center;
- }
- .projects-grid .project-meta {
- display: none;
- }
- .projects-grid .project {
- scroll-snap-align: start;
- border: 1px solid #000;
- box-shadow: 2px 2px 0 #000;
- background: linear-gradient(180deg, #fff, #f3f3f3);
- }
- .projects-details {
- margin: 0 6px 6px;
- }
- }
- </style>
|