chore: remove .claude/ from git tracking, add to .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 79450e4b35b9bf2f232034acf06774628ec4ae90
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2026 TechLatest (https://tech-latest.com)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
# windows-iso-downloader
|
|
||||||
|
|
||||||
> A clean, open-source tool for obtaining official Windows ISO files directly from Microsoft's CDN — without a Windows machine, the Media Creation Tool, or browser restrictions.
|
|
||||||
|
|
||||||
**Live:** [msdl.tech-latest.com](https://msdl.tech-latest.com) · **By:** [TechLatest](https://tech-latest.com)
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What it does
|
|
||||||
|
|
||||||
MSDL replicates the session flow that Microsoft uses to serve ISO download links — identical to the approach used by [Rufus/Fido](https://github.com/pbatard/Fido). You get the exact same signed CDN URL Microsoft would give you, just without the browser requirement.
|
|
||||||
|
|
||||||
- ✅ Direct Microsoft CDN links — no proxying of actual file data
|
|
||||||
- ✅ 38 languages per release
|
|
||||||
- ✅ ARM64 + x64 + x86 support
|
|
||||||
- ✅ No account, no browser lock, no ads, no tracking
|
|
||||||
- ✅ Links expire in 24 hours (Microsoft's standard behaviour, not a limitation)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
| Home | All Releases |
|
|
||||||
|---|---|
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
| Product detail — language selector | Product detail — download links |
|
|
||||||
|---|---|
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
windows-iso-downloader/
|
|
||||||
├── frontend/ # React 19 + TypeScript + Vite + Tailwind v4
|
|
||||||
├── backend/ # Go proxy server (recommended for production)
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
```
|
|
||||||
Browser → Backend Proxy → Microsoft Download API → Signed CDN URL
|
|
||||||
↓
|
|
||||||
1. Register session (Microsoft tracking endpoint)
|
|
||||||
2. Parse MDT fingerprint script
|
|
||||||
3. Fetch SKU list (available languages)
|
|
||||||
4. Fetch signed CDN download URL
|
|
||||||
```
|
|
||||||
|
|
||||||
The flow mirrors [Fido.ps1](https://github.com/pbatard/Fido) by Pete Batard — the same script bundled with Rufus.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Running Locally
|
|
||||||
|
|
||||||
### Backend (Go)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
go run main.go
|
|
||||||
# Runs on http://localhost:3002
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
# Runs on http://localhost:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `frontend/.env.local`:
|
|
||||||
|
|
||||||
```env
|
|
||||||
VITE_API_URL=http://localhost:3002
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### `GET /skuinfo?product_id=<id>`
|
|
||||||
|
|
||||||
Returns available languages for a product.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Skus": [
|
|
||||||
{
|
|
||||||
"Id": "0x0409",
|
|
||||||
"Language": "en-US",
|
|
||||||
"LocalizedLanguage": "English (United States)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `GET /proxy?product_id=<id>&sku_id=<sku>`
|
|
||||||
|
|
||||||
Returns signed download links from Microsoft's CDN.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ProductDownloadOptions": [
|
|
||||||
{
|
|
||||||
"Uri": "https://software.download.prss.microsoft.com/...",
|
|
||||||
"Architecture": "x64"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Supported Products
|
|
||||||
|
|
||||||
| Product | ID | Architecture |
|
|
||||||
|---|---|---|
|
|
||||||
| Windows 11 25H2 | 3262 | x64 |
|
|
||||||
| Windows 11 25H2 | 3265 | ARM64 |
|
|
||||||
| Windows 11 25H2 (updated) | 3321 | x64 |
|
|
||||||
| Windows 11 25H2 (updated) | 3324 | ARM64 |
|
|
||||||
| Windows 11 24H2 | 3113 | x64 |
|
|
||||||
| Windows 11 24H2 | 3131 | ARM64 |
|
|
||||||
| Windows 10 22H2 | 2618 | x64 / x86 |
|
|
||||||
| Windows 10 22H2 Home China | 2378 | x64 |
|
|
||||||
| Windows 8.1 | 52 | x64 / x86 |
|
|
||||||
| Windows 8.1 Single Language | 48 | x64 / x86 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
### Frontend (`frontend/`)
|
|
||||||
|
|
||||||
| | |
|
|
||||||
|---|---|
|
|
||||||
| Framework | React 19 + TypeScript |
|
|
||||||
| Build tool | Vite 8 |
|
|
||||||
| Styling | Tailwind CSS v4 |
|
|
||||||
| Animation | Motion (Framer Motion v12) |
|
|
||||||
| UI primitives | Radix UI |
|
|
||||||
| Toast | Sonner |
|
|
||||||
| Font | Geist |
|
|
||||||
| Router | React Router v7 |
|
|
||||||
|
|
||||||
### Backend (`backend/`)
|
|
||||||
|
|
||||||
| | |
|
|
||||||
|---|---|
|
|
||||||
| Language | Go 1.22+ |
|
|
||||||
| HTTP | `net/http` (stdlib, no framework) |
|
|
||||||
| Session cache | `sync.RWMutex` in-memory · 15 min TTL |
|
|
||||||
| UUID | `github.com/google/uuid` |
|
|
||||||
| MDT parsing | `regexp` (stdlib) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
> ⚠️ **Deploy the backend to a standard VPS** (Hetzner, DigitalOcean, Linode, etc.) — **not** serverless platforms like Vercel, Cloudflare Workers, or AWS Lambda. Microsoft rate-limits known datacenter IP ranges.
|
|
||||||
|
|
||||||
Recommended setup:
|
|
||||||
|
|
||||||
| Component | Platform |
|
|
||||||
|---|---|
|
|
||||||
| Frontend | Cloudflare Pages / Vercel (static) |
|
|
||||||
| Backend | VPS with a non-datacenter IP |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Pull requests welcome. To add a new Windows release:
|
|
||||||
|
|
||||||
1. Find the product ID on `www.microsoft.com/software-download-connector/api/`
|
|
||||||
2. Add it to `frontend/public/data/products.json`
|
|
||||||
3. Add related metadata in `ProductDetailPage.tsx` (`PRODUCT_META`, `RELATED_GROUPS`)
|
|
||||||
4. Update the product table in this README
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Disclaimer
|
|
||||||
|
|
||||||
This project is **not affiliated with, endorsed by, or sponsored by Microsoft Corporation**.
|
|
||||||
Windows is a registered trademark of Microsoft Corporation.
|
|
||||||
All ISO files are served directly from Microsoft's official CDN — this project does not host any files.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](./LICENSE) © [TechLatest](https://tech-latest.com)
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
const ALLOWED_HOSTS = [
|
|
||||||
"www.microsoft.com",
|
|
||||||
"vlscppe.microsoft.com",
|
|
||||||
"ov-df.microsoft.com",
|
|
||||||
];
|
|
||||||
|
|
||||||
export default {
|
|
||||||
async fetch(request) {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
|
|
||||||
// Target host comes from the ?host= param; path+search are forwarded as-is
|
|
||||||
const targetHost = url.searchParams.get("host");
|
|
||||||
if (!targetHost || !ALLOWED_HOSTS.includes(targetHost)) {
|
|
||||||
return new Response(JSON.stringify({ error: "Invalid or missing host parameter" }), {
|
|
||||||
status: 400,
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
url.searchParams.delete("host");
|
|
||||||
url.hostname = targetHost;
|
|
||||||
url.protocol = "https:";
|
|
||||||
url.port = "";
|
|
||||||
|
|
||||||
const upstreamRequest = new Request(url.toString(), {
|
|
||||||
method: request.method,
|
|
||||||
headers: {
|
|
||||||
"User-Agent": request.headers.get("User-Agent") || "Mozilla/5.0",
|
|
||||||
"Referer": request.headers.get("Referer") || "",
|
|
||||||
"Accept": request.headers.get("Accept") || "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const upstream = await fetch(upstreamRequest);
|
|
||||||
|
|
||||||
return new Response(upstream.body, {
|
|
||||||
status: upstream.status,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": upstream.headers.get("Content-Type") || "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
# Copy to .env.local and set your backend URL
|
|
||||||
VITE_API_URL=http://localhost:3002
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
# React + TypeScript + Vite
|
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
||||||
|
|
||||||
## React Compiler
|
|
||||||
|
|
||||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
|
||||||
tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// eslint.config.js
|
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
|
||||||
reactX.configs['recommended-typescript'],
|
|
||||||
// Enable lint rules for React DOM
|
|
||||||
reactDom.configs.recommended,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import js from '@eslint/js'
|
|
||||||
import globals from 'globals'
|
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
||||||
import tseslint from 'typescript-eslint'
|
|
||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
js.configs.recommended,
|
|
||||||
tseslint.configs.recommended,
|
|
||||||
reactHooks.configs.flat.recommended,
|
|
||||||
reactRefresh.configs.vite,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
globals: globals.browser,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Windows ISO Downloader | Official Microsoft Images</title>
|
|
||||||
<meta name="description" content="Download official Microsoft Windows ISO files for Windows 11, 10, and 8.1. Fast, free, no registration required." />
|
|
||||||
<meta property="og:site_name" content="MSDL — Windows ISO Downloader" />
|
|
||||||
<meta property="og:title" content="Windows ISO Downloader | Official Microsoft Images" />
|
|
||||||
<meta property="og:description" content="Download official Microsoft Windows ISO files. Fast, free, no registration." />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:url" content="https://msdl.tech-latest.com/" />
|
|
||||||
<meta property="og:image" content="https://msdl.tech-latest.com/og-image.png" />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:title" content="Windows ISO Downloader | Official Microsoft Images" />
|
|
||||||
<meta name="twitter:description" content="Download official Microsoft Windows ISO files. Fast, free, no registration." />
|
|
||||||
<meta name="twitter:image" content="https://msdl.tech-latest.com/og-image.png" />
|
|
||||||
<meta name="google-site-verification" content="THUig8dpFIXkQdfkC4k5dlkQjbKJI1SD5w--hdvZAu0" />
|
|
||||||
<meta name="theme-color" content="#111113" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "msdl-frontend-v2",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "tsc -b && vite build",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fontsource-variable/geist": "^5.2.8",
|
|
||||||
"@radix-ui/react-accordion": "^1.2.12",
|
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
|
||||||
"geist": "^1.7.0",
|
|
||||||
"lucide-react": "^1.7.0",
|
|
||||||
"motion": "^12.38.0",
|
|
||||||
"react": "^19.2.4",
|
|
||||||
"react-dom": "^19.2.4",
|
|
||||||
"react-router-dom": "^7.13.2",
|
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"tslib": "^2.8.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@eslint/js": "^9.39.4",
|
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
|
||||||
"@types/node": "^24.12.0",
|
|
||||||
"@types/react": "^19.2.14",
|
|
||||||
"@types/react-dom": "^19.2.3",
|
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
|
||||||
"eslint": "^9.39.4",
|
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
|
||||||
"globals": "^17.4.0",
|
|
||||||
"tailwindcss": "^4.2.2",
|
|
||||||
"typescript": "~5.9.3",
|
|
||||||
"typescript-eslint": "^8.57.0",
|
|
||||||
"vite": "^8.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/* /index.html 200
|
|
||||||
|
|
@ -1,207 +0,0 @@
|
||||||
{
|
|
||||||
"48": {
|
|
||||||
"name": "Windows 8.1 Single Language (9600.17415)",
|
|
||||||
"badge": "LEGACY",
|
|
||||||
"archs": [
|
|
||||||
"x64",
|
|
||||||
"x86"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"52"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"52": {
|
|
||||||
"name": "Windows 8.1 (9600.17415)",
|
|
||||||
"badge": "LEGACY",
|
|
||||||
"archs": [
|
|
||||||
"x64",
|
|
||||||
"x86"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"48",
|
|
||||||
"2618"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"2378": {
|
|
||||||
"name": "Windows 10 22H2 Home China (19045.2006)",
|
|
||||||
"badge": "EOL SOON",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"2618"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"2618": {
|
|
||||||
"name": "Windows 10 22H2 v1 (19045.2965)",
|
|
||||||
"badge": "EOL SOON",
|
|
||||||
"archs": [
|
|
||||||
"x64",
|
|
||||||
"x86"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3113",
|
|
||||||
"3262",
|
|
||||||
"52"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3113": {
|
|
||||||
"name": "Windows 11 24H2 (26100.1742)",
|
|
||||||
"badge": "STABLE",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3262",
|
|
||||||
"3131",
|
|
||||||
"2618"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3114": {
|
|
||||||
"name": "Windows 11 24H2 Home China (26100.1742)",
|
|
||||||
"badge": "STABLE",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3113",
|
|
||||||
"3115"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3115": {
|
|
||||||
"name": "Windows 11 24H2 Pro China (26100.1742)",
|
|
||||||
"badge": "STABLE",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3113",
|
|
||||||
"3114"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3131": {
|
|
||||||
"name": "Windows 11 Arm64 24H2 (26100.1742)",
|
|
||||||
"badge": "STABLE",
|
|
||||||
"archs": [
|
|
||||||
"ARM64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3265",
|
|
||||||
"3113"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3132": {
|
|
||||||
"name": "Windows 11 Arm64 24H2 Home China (26100.1742)",
|
|
||||||
"badge": "STABLE",
|
|
||||||
"archs": [
|
|
||||||
"ARM64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3131",
|
|
||||||
"3133"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3133": {
|
|
||||||
"name": "Windows 11 Arm64 24H2 Pro China (26100.1742)",
|
|
||||||
"badge": "STABLE",
|
|
||||||
"archs": [
|
|
||||||
"ARM64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3131",
|
|
||||||
"3132"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3262": {
|
|
||||||
"name": "Windows 11 25H2 (26200.6584)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3265",
|
|
||||||
"3321",
|
|
||||||
"3113"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3263": {
|
|
||||||
"name": "Windows 11 25H2 Home China (26200.6584)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3262",
|
|
||||||
"3264"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3264": {
|
|
||||||
"name": "Windows 11 25H2 Pro China (26200.6584)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3262",
|
|
||||||
"3263"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3265": {
|
|
||||||
"name": "Windows 11 Arm64 25H2 (26200.6584)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"ARM64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3262",
|
|
||||||
"3321",
|
|
||||||
"3131"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3266": {
|
|
||||||
"name": "Windows 11 Arm64 25H2 Home China (26200.6584)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"ARM64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3265",
|
|
||||||
"3267"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3267": {
|
|
||||||
"name": "Windows 11 Arm64 25H2 Pro China (26200.6584)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"ARM64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3265",
|
|
||||||
"3266"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3321": {
|
|
||||||
"name": "Windows 11 25H2 (Updated Oct)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3262",
|
|
||||||
"3324",
|
|
||||||
"3113"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3324": {
|
|
||||||
"name": "Windows 11 Arm64 25H2 (Updated Oct)",
|
|
||||||
"badge": "LATEST",
|
|
||||||
"archs": [
|
|
||||||
"ARM64"
|
|
||||||
],
|
|
||||||
"related": [
|
|
||||||
"3321",
|
|
||||||
"3262",
|
|
||||||
"3265"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 9.3 KiB |
|
|
@ -1,24 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
|
||||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
|
||||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
|
||||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
|
||||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
|
||||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
|
||||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
|
||||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
|
||||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
|
||||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
|
||||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
|
||||||
</symbol>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 693 KiB |
|
|
@ -1,4 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
Sitemap: https://msdl.tech-latest.com/sitemap.xml
|
|
||||||
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
|
@ -1,54 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
<!-- Main Static Pages -->
|
|
||||||
<url>
|
|
||||||
<loc>https://msdl.tech-latest.com/</loc>
|
|
||||||
<lastmod>2026-05-12</lastmod>
|
|
||||||
<priority>1.0</priority>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://msdl.tech-latest.com/products</loc>
|
|
||||||
<lastmod>2026-05-12</lastmod>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://msdl.tech-latest.com/about</loc>
|
|
||||||
<lastmod>2026-04-09</lastmod>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://msdl.tech-latest.com/privacy-policy</loc>
|
|
||||||
<lastmod>2026-04-09</lastmod>
|
|
||||||
<priority>0.3</priority>
|
|
||||||
<changefreq>yearly</changefreq>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://msdl.tech-latest.com/disclaimer</loc>
|
|
||||||
<lastmod>2026-04-09</lastmod>
|
|
||||||
<priority>0.3</priority>
|
|
||||||
<changefreq>yearly</changefreq>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Product Pages -->
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/48</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/52</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/2378</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/2618</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3113</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3114</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3115</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3131</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3132</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3133</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3262</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3263</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3264</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3265</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3266</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3267</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3321</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
<url><loc>https://msdl.tech-latest.com/product/3324</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
|
||||||
</urlset>
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import { Routes, Route } from 'react-router-dom'
|
|
||||||
import { Toaster } from 'sonner'
|
|
||||||
import Dock from './components/Dock'
|
|
||||||
import SiteFooter from './components/SiteFooter'
|
|
||||||
import HomePage from './pages/HomePage'
|
|
||||||
import ProductsPage from './pages/ProductsPage'
|
|
||||||
import ProductDetailPage from './pages/ProductDetailPage'
|
|
||||||
import AboutPage from './pages/AboutPage'
|
|
||||||
import PrivacyPolicyPage from './pages/PrivacyPolicyPage'
|
|
||||||
import DisclaimerPage from './pages/DisclaimerPage'
|
|
||||||
import NotFoundPage from './pages/NotFoundPage'
|
|
||||||
import ScrollToTop from './components/ScrollToTop'
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen pb-20 sm:pb-24 flex flex-col" style={{ overflowX: 'hidden', maxWidth: '100vw' }}>
|
|
||||||
<ScrollToTop />
|
|
||||||
<main className="flex-1">
|
|
||||||
<Routes>
|
|
||||||
<Route path="/" element={<HomePage />} />
|
|
||||||
<Route path="/products" element={<ProductsPage />} />
|
|
||||||
<Route path="/product/:productId" element={<ProductDetailPage />} />
|
|
||||||
<Route path="/about" element={<AboutPage />} />
|
|
||||||
<Route path="/privacy-policy" element={<PrivacyPolicyPage />} />
|
|
||||||
<Route path="/disclaimer" element={<DisclaimerPage />} />
|
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
|
||||||
</Routes>
|
|
||||||
</main>
|
|
||||||
<SiteFooter />
|
|
||||||
<Dock />
|
|
||||||
<Toaster
|
|
||||||
position="bottom-center"
|
|
||||||
offset={96}
|
|
||||||
theme="dark"
|
|
||||||
toastOptions={{
|
|
||||||
style: {
|
|
||||||
background: '#18181b',
|
|
||||||
border: '1px solid rgba(255,255,255,0.09)',
|
|
||||||
color: '#fafafa',
|
|
||||||
fontFamily: 'var(--font-sans)',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 44 KiB |
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
|
@ -1,78 +0,0 @@
|
||||||
import { useState } from 'react'
|
|
||||||
import { motion, AnimatePresence } from 'motion/react'
|
|
||||||
import { Terminal, ChevronRight, Copy, Check } from 'lucide-react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
interface Aria2TipProps {
|
|
||||||
downloadUrl?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Aria2Tip({ downloadUrl }: Aria2TipProps) {
|
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
const [copied, setCopied] = useState(false)
|
|
||||||
|
|
||||||
const urlPlaceholder = downloadUrl || 'PASTE_YOUR_LINK_HERE'
|
|
||||||
const command = `aria2c -x 16 -s 16 --disable-ipv6=true "${urlPlaceholder}"`
|
|
||||||
|
|
||||||
function handleCopy() {
|
|
||||||
navigator.clipboard.writeText(command)
|
|
||||||
setCopied(true)
|
|
||||||
toast.success('Command copied!')
|
|
||||||
setTimeout(() => setCopied(false), 2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mt-3">
|
|
||||||
<button
|
|
||||||
onClick={() => setOpen(!open)}
|
|
||||||
className="flex items-center gap-2 text-[12px] text-zinc-600 hover:text-zinc-400 transition-colors py-1"
|
|
||||||
>
|
|
||||||
<Terminal size={13} />
|
|
||||||
<span>Download faster with aria2 — 16× parallel connections</span>
|
|
||||||
<motion.span
|
|
||||||
animate={{ rotate: open ? 90 : 0 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
|
|
||||||
>
|
|
||||||
<ChevronRight size={13} />
|
|
||||||
</motion.span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<AnimatePresence initial={false}>
|
|
||||||
{open && (
|
|
||||||
<motion.div
|
|
||||||
key="aria2-content"
|
|
||||||
initial={{ height: 0, opacity: 0 }}
|
|
||||||
animate={{ height: 'auto', opacity: 1 }}
|
|
||||||
exit={{ height: 0, opacity: 0 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
|
||||||
className="overflow-hidden"
|
|
||||||
>
|
|
||||||
<div className="mt-2 rounded-xl border border-white/7 bg-[#0d0d0f] overflow-hidden">
|
|
||||||
<div className="flex items-center justify-between px-4 py-2 border-b border-white/5">
|
|
||||||
<span className="text-[10px] font-mono text-zinc-600 uppercase tracking-widest">Terminal</span>
|
|
||||||
<button
|
|
||||||
onClick={handleCopy}
|
|
||||||
className="flex items-center gap-1.5 text-[11px] text-zinc-500 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{copied ? <Check size={11} className="text-green-400" /> : <Copy size={11} />}
|
|
||||||
<span>{copied ? 'Copied' : 'Copy'}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-3 overflow-x-auto">
|
|
||||||
<code className="text-[12px] font-mono text-zinc-300 whitespace-nowrap">
|
|
||||||
<span className="text-zinc-600 select-none">$ </span>
|
|
||||||
{command}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
{!downloadUrl && (
|
|
||||||
<p className="px-4 pb-3 text-[11px] text-zinc-600">
|
|
||||||
Generate a link above, then paste it into this command for 16× speed.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
interface BadgeProps {
|
|
||||||
variant: 'latest' | 'stable' | 'eol' | 'legacy'
|
|
||||||
label?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
latest: {
|
|
||||||
label: 'LATEST',
|
|
||||||
className: 'bg-blue-500/15 text-blue-400 border-blue-500/25',
|
|
||||||
},
|
|
||||||
stable: {
|
|
||||||
label: 'STABLE',
|
|
||||||
className: 'bg-green-500/15 text-green-400 border-green-500/25',
|
|
||||||
},
|
|
||||||
eol: {
|
|
||||||
label: 'END OF LIFE',
|
|
||||||
className: 'bg-amber-500/15 text-amber-400 border-amber-500/25',
|
|
||||||
},
|
|
||||||
legacy: {
|
|
||||||
label: 'LEGACY',
|
|
||||||
className: 'bg-zinc-500/15 text-zinc-400 border-zinc-500/25',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Badge({ variant, label }: BadgeProps) {
|
|
||||||
const { label: defaultLabel, className } = config[variant]
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={`inline-flex items-center px-2 py-0.5 rounded-full border text-[10px] font-semibold tracking-widest font-mono ${className}`}
|
|
||||||
>
|
|
||||||
{label ?? defaultLabel}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { Check, X, Minus } from 'lucide-react'
|
|
||||||
|
|
||||||
type CellValue = true | false | 'varies'
|
|
||||||
|
|
||||||
interface Row {
|
|
||||||
feature: string
|
|
||||||
msdl: CellValue
|
|
||||||
official: CellValue
|
|
||||||
others: CellValue
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows: Row[] = [
|
|
||||||
{ feature: 'Direct CDN link', msdl: true, official: true, others: false },
|
|
||||||
{ feature: 'No browser required', msdl: true, official: false, others: 'varies' },
|
|
||||||
{ feature: 'ARM64 support', msdl: true, official: false, others: false },
|
|
||||||
{ feature: 'No account needed', msdl: true, official: true, others: 'varies' },
|
|
||||||
{ feature: 'No ads or tracking', msdl: true, official: true, others: false },
|
|
||||||
]
|
|
||||||
|
|
||||||
function Cell({ value, highlight = false }: { value: CellValue; highlight?: boolean }) {
|
|
||||||
if (value === true)
|
|
||||||
return <Check size={15} className={highlight ? 'text-blue-400' : 'text-green-500'} />
|
|
||||||
if (value === false)
|
|
||||||
return <X size={15} className="text-zinc-600" />
|
|
||||||
return <Minus size={15} className="text-zinc-500" />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ComparisonTable() {
|
|
||||||
return (
|
|
||||||
<section className="mt-20">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 16 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true, margin: '-60px' }}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
>
|
|
||||||
<h2 className="text-xl font-semibold text-white mb-1">Why MSDL?</h2>
|
|
||||||
<p className="text-sm text-zinc-500 mb-8">Compared to your other options.</p>
|
|
||||||
|
|
||||||
{/* Scrollable wrapper on mobile to prevent page-level overflow */}
|
|
||||||
<div className="overflow-x-auto rounded-2xl border border-white/7" style={{ WebkitOverflowScrolling: 'touch' }}>
|
|
||||||
<div style={{ minWidth: '480px' }}>
|
|
||||||
{/* Header */}
|
|
||||||
<div className="grid grid-cols-4 bg-[#111113] border-b border-white/7 text-[11px] font-mono font-semibold uppercase tracking-widest">
|
|
||||||
<div className="px-4 py-3 text-zinc-600">Feature</div>
|
|
||||||
<div className="px-4 py-3 text-blue-400 bg-blue-500/5 border-x border-blue-500/10 text-center">MSDL</div>
|
|
||||||
<div className="px-4 py-3 text-zinc-500 text-center">Official</div>
|
|
||||||
<div className="px-4 py-3 text-zinc-500 text-center">Others</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Rows */}
|
|
||||||
{rows.map((row, i) => (
|
|
||||||
<div
|
|
||||||
key={row.feature}
|
|
||||||
className={`grid grid-cols-4 border-b border-white/5 last:border-0 ${i % 2 === 0 ? 'bg-[#0d0d0f]' : 'bg-[#111113]'}`}
|
|
||||||
>
|
|
||||||
<div className="px-4 py-3.5 text-sm text-zinc-400">{row.feature}</div>
|
|
||||||
<div className="px-4 py-3.5 flex justify-center bg-blue-500/3 border-x border-blue-500/8">
|
|
||||||
<Cell value={row.msdl} highlight />
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-3.5 flex justify-center">
|
|
||||||
<Cell value={row.official} />
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-3.5 flex justify-center">
|
|
||||||
<Cell value={row.others} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="mt-2 text-[11px] text-zinc-700 text-right pr-1 sm:hidden">← scroll →</p>
|
|
||||||
</motion.div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { Home, LayoutGrid, Info, ExternalLink, Shield } from 'lucide-react'
|
|
||||||
|
|
||||||
interface DockItem {
|
|
||||||
label: string
|
|
||||||
icon: React.ReactNode
|
|
||||||
path?: string
|
|
||||||
href?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const items: DockItem[] = [
|
|
||||||
{ label: 'Home', icon: <Home size={19} />, path: '/' },
|
|
||||||
{ label: 'Products', icon: <LayoutGrid size={19} />, path: '/products' },
|
|
||||||
{ label: 'About', icon: <Info size={19} />, path: '/about' },
|
|
||||||
{ label: 'Privacy', icon: <Shield size={19} />, path: '/privacy-policy' },
|
|
||||||
{ label: 'TechLatest', icon: <ExternalLink size={19} />, href: 'https://tech-latest.com' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function Dock() {
|
|
||||||
const location = useLocation()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const [activeIndex, setActiveIndex] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const idx = items.findIndex(item => item.path && item.path === location.pathname)
|
|
||||||
if (idx !== -1) setActiveIndex(idx)
|
|
||||||
}, [location.pathname])
|
|
||||||
|
|
||||||
function handleClick(item: DockItem, index: number) {
|
|
||||||
setActiveIndex(index)
|
|
||||||
if (item.href) {
|
|
||||||
window.open(item.href, '_blank', 'noopener noreferrer')
|
|
||||||
} else if (item.path) {
|
|
||||||
navigate(item.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* ── DESKTOP DOCK (sm+) — centered floating pill ── */}
|
|
||||||
<div className="hidden sm:flex fixed bottom-5 left-0 right-0 justify-center z-50 pointer-events-none">
|
|
||||||
<motion.div
|
|
||||||
className="pointer-events-auto flex items-center gap-1 px-3 py-2 rounded-2xl border border-white/10 bg-[#111113]/90 backdrop-blur-xl shadow-2xl shadow-black/50"
|
|
||||||
initial={{ y: 16, opacity: 0 }}
|
|
||||||
animate={{ y: 0, opacity: 1 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
|
||||||
>
|
|
||||||
{items.map((item, i) => {
|
|
||||||
const isActive = i === activeIndex
|
|
||||||
return (
|
|
||||||
<motion.button
|
|
||||||
key={item.label}
|
|
||||||
onClick={() => handleClick(item, i)}
|
|
||||||
whileHover={{ scale: 1.06, y: -2 }}
|
|
||||||
whileTap={{ scale: 0.93 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
|
|
||||||
className={`
|
|
||||||
relative flex items-center gap-2 rounded-xl cursor-pointer px-3 py-2
|
|
||||||
transition-all duration-200
|
|
||||||
${isActive
|
|
||||||
? 'bg-white/10 border border-white/15 text-white'
|
|
||||||
: 'border border-transparent text-zinc-500 hover:text-zinc-300'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
aria-label={item.label}
|
|
||||||
>
|
|
||||||
{item.icon}
|
|
||||||
|
|
||||||
{/* Expanding active label */}
|
|
||||||
<motion.span
|
|
||||||
animate={{ width: isActive ? 'auto' : 0, opacity: isActive ? 1 : 0 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 400, damping: 32 }}
|
|
||||||
className="overflow-hidden whitespace-nowrap text-sm font-medium"
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</motion.span>
|
|
||||||
|
|
||||||
{/* Active dot */}
|
|
||||||
{isActive && (
|
|
||||||
<motion.span
|
|
||||||
layoutId="dock-dot-desktop"
|
|
||||||
className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-blue-400"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</motion.button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ── MOBILE DOCK (<sm) — full-width bottom bar, flush, no gap ── */}
|
|
||||||
<motion.div
|
|
||||||
className="flex sm:hidden fixed bottom-0 left-0 right-0 z-50
|
|
||||||
border-t border-white/8 bg-[#111113]/95 backdrop-blur-xl"
|
|
||||||
initial={{ y: 20, opacity: 0 }}
|
|
||||||
animate={{ y: 0, opacity: 1 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
|
||||||
>
|
|
||||||
{items.map((item, i) => {
|
|
||||||
const isActive = i === activeIndex
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={item.label}
|
|
||||||
onClick={() => handleClick(item, i)}
|
|
||||||
className="flex-1 flex flex-col items-center justify-center gap-1 py-3 min-w-0"
|
|
||||||
>
|
|
||||||
<span className={`transition-colors ${isActive ? 'text-blue-400' : 'text-zinc-500'}`}>
|
|
||||||
{item.icon}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`text-[9px] font-medium tracking-wide transition-colors truncate max-w-full px-1 ${
|
|
||||||
isActive ? 'text-blue-400' : 'text-zinc-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</motion.div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
import { useState } from 'react'
|
|
||||||
import { motion, AnimatePresence } from 'motion/react'
|
|
||||||
import { ChevronRight } from 'lucide-react'
|
|
||||||
|
|
||||||
interface FaqItem {
|
|
||||||
q: string
|
|
||||||
a: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const faqs: FaqItem[] = [
|
|
||||||
{
|
|
||||||
q: 'Is this legal?',
|
|
||||||
a: 'Yes. MSDL does not host, modify, or redistribute any Microsoft files. It automates access to download links that Microsoft already provides for free at microsoft.com/software-download — the same way Rufus and the open-source Fido script do. Microsoft itself recommends Rufus (which uses the identical approach) in its official documentation. You are downloading a file directly from Microsoft\'s own servers. A valid Windows license is still required to activate the OS.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: 'Are these download links safe?',
|
|
||||||
a: 'Yes. MSDL never stores or modifies any files. It retrieves signed, time-limited download links directly from Microsoft\'s servers (software.download.prss.microsoft.com). You can verify this by inspecting your browser\'s network requests.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: 'Why do links expire in 24 hours?',
|
|
||||||
a: 'Microsoft generates short-lived, IP-tied signed URLs to prevent redistribution and hotlinking of their ISOs. This is the same link you\'d get from the official Microsoft Software Download page — we just make it accessible without a browser.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: 'Can I use this with IDM or aria2?',
|
|
||||||
a: 'Absolutely — and we strongly recommend it. Microsoft throttles single-threaded browser downloads. Tools like aria2 (aria2c -x 16 -s 16 --disable-ipv6=true "URL") or IDM use 16 parallel connections, which typically give you full line speed on their CDN.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: 'What\'s the difference between the various product IDs?',
|
|
||||||
a: 'Each Windows release has a unique product ID on Microsoft\'s servers. For example, 3262 is Windows 11 25H2 (x64) and 3265 is Windows 11 25H2 (ARM64). MSDL exposes all known IDs so you can always find the exact build you need.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: 'Is MSDL open source?',
|
|
||||||
a: 'Yes. Both the frontend and the backend proxy are fully open source. The backend implements the same session-based flow as the Fido PowerShell script, ported to Go and Node.js. You can self-host the entire stack.',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function FAQAccordion() {
|
|
||||||
const [open, setOpen] = useState<number | null>(null)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="mt-20 mb-8">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 16 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true, margin: '-60px' }}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
>
|
|
||||||
<h2 className="text-xl font-semibold text-white mb-1">Frequently asked questions</h2>
|
|
||||||
<p className="text-sm text-zinc-500 mb-8">Everything you need to know before downloading.</p>
|
|
||||||
|
|
||||||
<div className="rounded-2xl border border-white/7 overflow-hidden divide-y divide-white/5">
|
|
||||||
{faqs.map((faq, i) => (
|
|
||||||
<div key={i} className="bg-[#111113]">
|
|
||||||
<button
|
|
||||||
onClick={() => setOpen(open === i ? null : i)}
|
|
||||||
className="w-full flex items-center justify-between px-5 py-4 text-left group"
|
|
||||||
>
|
|
||||||
<span className={`text-sm font-medium transition-colors ${open === i ? 'text-white' : 'text-zinc-300 group-hover:text-white'}`}>
|
|
||||||
{faq.q}
|
|
||||||
</span>
|
|
||||||
<motion.span
|
|
||||||
animate={{ rotate: open === i ? 90 : 0 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
|
|
||||||
className="flex-shrink-0 ml-4 text-zinc-600 group-hover:text-zinc-400 transition-colors"
|
|
||||||
>
|
|
||||||
<ChevronRight size={16} />
|
|
||||||
</motion.span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<AnimatePresence initial={false}>
|
|
||||||
{open === i && (
|
|
||||||
<motion.div
|
|
||||||
key="content"
|
|
||||||
initial={{ height: 0, opacity: 0 }}
|
|
||||||
animate={{ height: 'auto', opacity: 1 }}
|
|
||||||
exit={{ height: 0, opacity: 0 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
|
||||||
className="overflow-hidden"
|
|
||||||
>
|
|
||||||
<p className="px-5 pb-5 text-sm text-zinc-400 leading-relaxed">
|
|
||||||
{faq.a}
|
|
||||||
</p>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { LayoutGrid, Globe, Link2 } from 'lucide-react'
|
|
||||||
|
|
||||||
const steps = [
|
|
||||||
{
|
|
||||||
num: '1',
|
|
||||||
icon: LayoutGrid,
|
|
||||||
title: 'Pick a release',
|
|
||||||
desc: 'Choose from Windows 11, 10, and 8.1 across all feature updates.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
num: '2',
|
|
||||||
icon: Globe,
|
|
||||||
title: 'Choose language',
|
|
||||||
desc: '38 localised languages available for every release.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
num: '3',
|
|
||||||
icon: Link2,
|
|
||||||
title: 'Get CDN link',
|
|
||||||
desc: 'Direct from Microsoft\'s servers. No proxy, no middleman.',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function HowItWorks() {
|
|
||||||
return (
|
|
||||||
<section className="mt-20">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 16 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true, margin: '-60px' }}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
>
|
|
||||||
<h2 className="text-xl font-semibold text-white mb-1">How it works</h2>
|
|
||||||
<p className="text-sm text-zinc-500 mb-8">Three steps to your official Windows ISO.</p>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-px bg-white/7 rounded-2xl overflow-hidden border border-white/7">
|
|
||||||
{steps.map((step, i) => {
|
|
||||||
const Icon = step.icon
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={step.num}
|
|
||||||
className="bg-[#111113] p-6 flex flex-col gap-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-[11px] font-mono font-semibold text-blue-400">
|
|
||||||
{i + 1}
|
|
||||||
</span>
|
|
||||||
<Icon size={16} className="text-zinc-500" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-semibold text-white mb-1">{step.title}</p>
|
|
||||||
<p className="text-sm text-zinc-500 leading-relaxed">{step.desc}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { Download, ArrowRight } from 'lucide-react'
|
|
||||||
import Badge from './Badge'
|
|
||||||
|
|
||||||
const WinLogo = ({ className }: { className?: string }) => (
|
|
||||||
<svg viewBox="0 0 88 88" className={className} fill="currentColor">
|
|
||||||
<path d="M0 12.402l35.687-4.86.016 34.423-35.67.203zm35.67 33.529l.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
type BadgeVariant = 'latest' | 'stable' | 'eol' | 'legacy'
|
|
||||||
|
|
||||||
interface ProductCardProps {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
version: string
|
|
||||||
build: string
|
|
||||||
description: string
|
|
||||||
badge: BadgeVariant
|
|
||||||
archs?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ProductCard({
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
build,
|
|
||||||
description,
|
|
||||||
badge,
|
|
||||||
archs = ['x64'],
|
|
||||||
}: ProductCardProps) {
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
whileHover={{ y: -3, scale: 1.005 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
|
|
||||||
className="group relative cursor-pointer rounded-2xl border border-white/7 bg-[#111113] p-6
|
|
||||||
hover:border-white/13 hover:shadow-2xl hover:shadow-black/40
|
|
||||||
transition-colors duration-300"
|
|
||||||
>
|
|
||||||
{/* Header row */}
|
|
||||||
<div className="flex items-start justify-between mb-4">
|
|
||||||
<div className="w-9 h-9 rounded-lg bg-white/5 border border-white/8 flex items-center justify-center">
|
|
||||||
<WinLogo className="w-5 h-5 text-white/60" />
|
|
||||||
</div>
|
|
||||||
<Badge variant={badge} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Title */}
|
|
||||||
<div className="mb-3">
|
|
||||||
<h2 className="text-base font-semibold text-white leading-tight">{name} {version}</h2>
|
|
||||||
<p className="text-[11px] font-mono text-zinc-500 mt-0.5 tracking-wide">Build {build}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
<p className="text-sm text-zinc-400 leading-relaxed mb-4">{description}</p>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-1.5">
|
|
||||||
{archs.map(arch => (
|
|
||||||
<span
|
|
||||||
key={arch}
|
|
||||||
className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded bg-white/5 border border-white/8 text-zinc-500"
|
|
||||||
>
|
|
||||||
{arch}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1 text-xs text-zinc-600 group-hover:text-zinc-400 transition-colors">
|
|
||||||
<Download size={12} />
|
|
||||||
<span>Get links</span>
|
|
||||||
<ArrowRight size={11} className="group-hover:translate-x-0.5 transition-transform" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
import { ArrowRight } from 'lucide-react'
|
|
||||||
|
|
||||||
interface RelatedProduct {
|
|
||||||
id: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RelatedReleasesProps {
|
|
||||||
current: string
|
|
||||||
items: RelatedProduct[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RelatedReleases({ current, items }: RelatedReleasesProps) {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const filtered = items.filter(i => i.id !== current)
|
|
||||||
if (filtered.length === 0) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mt-6 pt-5 border-t border-white/5">
|
|
||||||
<p className="text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-3">
|
|
||||||
Also available
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{filtered.map(item => (
|
|
||||||
<button
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => navigate(`/product/${item.id}`)}
|
|
||||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-white/7 bg-white/3 text-xs text-zinc-400 hover:text-white hover:border-white/13 hover:bg-white/6 transition-all"
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
<ArrowRight size={11} />
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
|
|
||||||
export default function ScrollToTop() {
|
|
||||||
const { pathname } = useLocation()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.scrollTo({ top: 0, behavior: 'instant' })
|
|
||||||
}, [pathname])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
|
|
||||||
export default function SiteFooter() {
|
|
||||||
return (
|
|
||||||
<footer className="max-w-4xl mx-auto px-5 pb-4 pt-8 text-center">
|
|
||||||
<p className="text-[11px] text-zinc-600 leading-relaxed">
|
|
||||||
This open-source project is not affiliated with Microsoft Corporation.{' '}
|
|
||||||
Windows is a registered trademark of Microsoft Corporation.{' '}
|
|
||||||
All ISO files are hosted on Microsoft's official CDN.
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center justify-center gap-4 mt-2 text-[11px] text-zinc-700">
|
|
||||||
<Link to="/privacy-policy" className="hover:text-zinc-500 transition-colors">Privacy Policy</Link>
|
|
||||||
<span>·</span>
|
|
||||||
<Link to="/disclaimer" className="hover:text-zinc-500 transition-colors">Disclaimer</Link>
|
|
||||||
<span>·</span>
|
|
||||||
<Link to="/about" className="hover:text-zinc-500 transition-colors">About</Link>
|
|
||||||
<span>·</span>
|
|
||||||
<a
|
|
||||||
href="https://github.com/starkSV/windows-iso-downloader"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="hover:text-zinc-500 transition-colors"
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
|
|
||||||
interface Stat {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function StatsBar() {
|
|
||||||
const [totalReleases, setTotalReleases] = useState('17')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('/data/products.json')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then((data: Record<string, { active?: boolean }>) => {
|
|
||||||
const activeCount = Object.values(data).filter(p => p.active !== false).length
|
|
||||||
setTotalReleases(activeCount.toString())
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const stats: Stat[] = [
|
|
||||||
{ value: totalReleases, label: 'releases available' },
|
|
||||||
{ value: '38', label: 'languages' },
|
|
||||||
{ value: '0', label: 'intermediaries' },
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5 text-[11px] font-mono text-zinc-600"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ delay: 0.35, duration: 0.5 }}
|
|
||||||
>
|
|
||||||
{stats.map((s, i) => (
|
|
||||||
<span key={s.label} className="flex items-center gap-4">
|
|
||||||
<span>
|
|
||||||
<span className="text-zinc-400 font-semibold">{s.value}</span>
|
|
||||||
{' '}{s.label}
|
|
||||||
</span>
|
|
||||||
{i < stats.length - 1 && <span className="text-zinc-700">·</span>}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { Cpu, MemoryStick, HardDrive, Shield } from 'lucide-react'
|
|
||||||
|
|
||||||
interface Requirement {
|
|
||||||
icon: React.ReactNode
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SystemRequirementsProps {
|
|
||||||
isWin11?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SystemRequirements({ isWin11 = false }: SystemRequirementsProps) {
|
|
||||||
const requirements: Requirement[] = [
|
|
||||||
{ icon: <Cpu size={16} />, value: '1 GHz+', label: 'Processor' },
|
|
||||||
{ icon: <MemoryStick size={16} />, value: isWin11 ? '4 GB' : '2 GB', label: 'RAM' },
|
|
||||||
{ icon: <HardDrive size={16} />, value: '64 GB', label: 'Storage' },
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rounded-xl border border-white/7 bg-[#111113] p-4 mt-5">
|
|
||||||
<p className="text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-3">
|
|
||||||
System Requirements
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-3 gap-3">
|
|
||||||
{requirements.map(req => (
|
|
||||||
<div key={req.label} className="flex flex-col gap-1.5">
|
|
||||||
<span className="text-zinc-500">{req.icon}</span>
|
|
||||||
<span className="text-sm font-semibold text-white">{req.value}</span>
|
|
||||||
<span className="text-[11px] text-zinc-600">{req.label}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{isWin11 && (
|
|
||||||
<div className="mt-3 pt-3 border-t border-white/5 flex items-center gap-2">
|
|
||||||
<Shield size={13} className="text-amber-500 flex-shrink-0" />
|
|
||||||
<span className="text-[11px] text-amber-500/80">TPM 2.0 required for Windows 11</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
@import "tailwindcss";
|
|
||||||
@import "@fontsource-variable/geist";
|
|
||||||
|
|
||||||
@theme {
|
|
||||||
/* Background layers */
|
|
||||||
--color-bg: #09090b;
|
|
||||||
--color-surface: #111113;
|
|
||||||
--color-surface-2: #18181b;
|
|
||||||
|
|
||||||
/* Borders */
|
|
||||||
--color-border: rgba(255, 255, 255, 0.07);
|
|
||||||
--color-border-hover: rgba(255, 255, 255, 0.13);
|
|
||||||
|
|
||||||
/* Accent */
|
|
||||||
--color-accent: #3b82f6;
|
|
||||||
--color-accent-hover: #2563eb;
|
|
||||||
|
|
||||||
/* Text */
|
|
||||||
--color-text: #fafafa;
|
|
||||||
--color-text-2: #a1a1aa;
|
|
||||||
--color-text-muted: #52525b;
|
|
||||||
|
|
||||||
/* Status */
|
|
||||||
--color-success: #22c55e;
|
|
||||||
--color-warning: #f59e0b;
|
|
||||||
--color-error: #ef4444;
|
|
||||||
|
|
||||||
/* Font families */
|
|
||||||
--font-sans: 'Geist Variable', 'Geist', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
|
||||||
--font-mono: 'Geist Mono Variable', 'Geist Mono', 'JetBrains Mono', ui-monospace, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
background-color: #09090b;
|
|
||||||
color: #fafafa;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
scroll-padding-bottom: 120px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Thin, minimal scrollbar */
|
|
||||||
::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
||||||
::-webkit-scrollbar-track { background: transparent; }
|
|
||||||
::-webkit-scrollbar-thumb { background: #27272a; border-radius: 2px; }
|
|
||||||
::-webkit-scrollbar-thumb:hover { background: #3f3f46; }
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { StrictMode } from 'react'
|
|
||||||
import { createRoot } from 'react-dom/client'
|
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
|
||||||
import './index.css'
|
|
||||||
import App from './App.tsx'
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
|
||||||
<StrictMode>
|
|
||||||
<BrowserRouter>
|
|
||||||
<App />
|
|
||||||
</BrowserRouter>
|
|
||||||
</StrictMode>,
|
|
||||||
)
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
import { motion } from 'motion/react'
|
|
||||||
|
|
||||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
|
||||||
|
|
||||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-2.5">
|
|
||||||
<h2 className="text-sm font-semibold text-white/80">{title}</h2>
|
|
||||||
<div className="text-sm text-zinc-500 leading-relaxed space-y-2">{children}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AboutPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<title>About MSDL | Windows ISO Downloader</title>
|
|
||||||
<meta name="description" content="Learn about MSDL, an open-source tool for fetching official Windows ISOs directly from Microsoft." />
|
|
||||||
<link rel="canonical" href={`${SITE_URL}/about`} />
|
|
||||||
<meta property="og:title" content="About MSDL | Windows ISO Downloader" />
|
|
||||||
<meta property="og:description" content="Learn about MSDL, an open-source tool for fetching official Windows ISOs directly from Microsoft." />
|
|
||||||
<meta property="og:url" content={`${SITE_URL}/about`} />
|
|
||||||
|
|
||||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 16 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.35 }}
|
|
||||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold text-white mb-1">About MSDL</h1>
|
|
||||||
<p className="text-xs font-mono text-zinc-600">A TechLatest open-source project</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Section title="What is this?">
|
|
||||||
<p>
|
|
||||||
MSDL (Microsoft Software Download Links) is a clean, open-source web tool for
|
|
||||||
obtaining official Windows ISO files directly from Microsoft's content delivery network
|
|
||||||
— without needing a Windows machine, the Media Creation Tool, or a browser lock check.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
There are no third-party hosts, no redirects, no registration, and no ads.
|
|
||||||
Every link you receive is exactly the same signed URL you would get from
|
|
||||||
Microsoft's own download page.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="How it works">
|
|
||||||
<p>
|
|
||||||
Our backend replicates the session-based authentication flow that Microsoft uses to
|
|
||||||
serve download links to end users. The same approach is used by{' '}
|
|
||||||
<a
|
|
||||||
href="https://github.com/pbatard/Fido"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
|
||||||
>
|
|
||||||
Fido
|
|
||||||
</a>{' '}
|
|
||||||
(the PowerShell script bundled with Rufus). The flow is:
|
|
||||||
</p>
|
|
||||||
<ol className="list-decimal list-inside space-y-1 text-zinc-500 text-sm">
|
|
||||||
<li>Register a session with Microsoft's tracking endpoint</li>
|
|
||||||
<li>Fetch and parse the MDT fingerprinting script</li>
|
|
||||||
<li>Call the SKU info API to retrieve available languages</li>
|
|
||||||
<li>Call the download links API using the warmed session to get signed CDN URLs</li>
|
|
||||||
</ol>
|
|
||||||
<p>
|
|
||||||
Links are IP-tied to the server and expire after 24 hours — this is standard
|
|
||||||
Microsoft behaviour, not a limitation of MSDL.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Data source">
|
|
||||||
<p>
|
|
||||||
All product data (release names, build numbers, and available architectures) is sourced
|
|
||||||
from Microsoft's official software download connector API:
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<code className="text-[11px] font-mono bg-white/5 border border-white/7 px-2 py-0.5 rounded text-zinc-400 break-all">
|
|
||||||
www.microsoft.com/software-download-connector/api/
|
|
||||||
</code>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Product IDs are maintained manually based on Microsoft's release cadence.
|
|
||||||
Windows 11 25H2, 24H2, Windows 10 22H2, and Windows 8.1 are currently listed.
|
|
||||||
New releases are added as Microsoft publishes them.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Credits">
|
|
||||||
<p>
|
|
||||||
The underlying session flow is inspired by{' '}
|
|
||||||
<a
|
|
||||||
href="https://github.com/pbatard/Fido"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
|
||||||
>
|
|
||||||
Fido
|
|
||||||
</a>{' '}
|
|
||||||
by Pete Batard, the same mechanism powering Rufus's ISO download feature.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Built and maintained by{' '}
|
|
||||||
<a
|
|
||||||
href="https://tech-latest.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
|
||||||
>
|
|
||||||
TechLatest
|
|
||||||
</a>.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
import { motion } from 'motion/react'
|
|
||||||
|
|
||||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
|
||||||
|
|
||||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h2 className="text-sm font-semibold text-white/80">{title}</h2>
|
|
||||||
<div className="text-sm text-zinc-500 leading-relaxed space-y-2">{children}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DisclaimerPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<title>Disclaimer | Windows ISO Downloader</title>
|
|
||||||
<meta name="description" content="Legal disclaimer for MSDL. Not affiliated with Microsoft Corporation." />
|
|
||||||
<link rel="canonical" href={`${SITE_URL}/disclaimer`} />
|
|
||||||
<meta property="og:title" content="Disclaimer | Windows ISO Downloader" />
|
|
||||||
<meta property="og:description" content="Legal disclaimer for MSDL. Not affiliated with Microsoft Corporation." />
|
|
||||||
<meta property="og:url" content={`${SITE_URL}/disclaimer`} />
|
|
||||||
|
|
||||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 16 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.35 }}
|
|
||||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold text-white mb-1">Disclaimer</h1>
|
|
||||||
<p className="text-xs font-mono text-zinc-600">Last updated: April 9, 2026</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 rounded-xl border border-amber-500/15 bg-amber-500/6 text-amber-400/80 text-[12px] leading-relaxed">
|
|
||||||
This open-source project is <strong className="text-amber-400">not affiliated with, endorsed by, or sponsored by
|
|
||||||
Microsoft Corporation</strong> in any way. Windows is a registered trademark of Microsoft Corporation.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Section title="No affiliation with Microsoft">
|
|
||||||
<p>
|
|
||||||
MSDL is an independent, open-source project. It is not produced, approved, or supported
|
|
||||||
by Microsoft Corporation. The name "Windows" and the Windows logo are registered
|
|
||||||
trademarks of Microsoft Corporation.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Purpose">
|
|
||||||
<p>
|
|
||||||
MSDL exists solely to make it easier for users to access official, unmodified Microsoft
|
|
||||||
Windows ISO files that Microsoft already makes freely available for download. All files
|
|
||||||
are served directly from Microsoft's own servers — we do not host, mirror, or modify
|
|
||||||
any content.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="No warranty">
|
|
||||||
<p>
|
|
||||||
This tool is provided "as is" without warranty of any kind. We make no guarantees
|
|
||||||
about the availability of Microsoft's download links, the uptime of our backend, or
|
|
||||||
the compatibility of any downloaded ISO with your hardware configuration.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Use at your own risk">
|
|
||||||
<p>
|
|
||||||
By using this tool, you agree that you are solely responsible for complying with
|
|
||||||
Microsoft's End User License Agreement (EULA) and any applicable laws in your jurisdiction.
|
|
||||||
Downloading Windows does not grant you a license to use it — a valid product key or
|
|
||||||
digital license is required for activation.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Open source">
|
|
||||||
<p>
|
|
||||||
The source code is publicly available. You are free to inspect, fork, and self-host
|
|
||||||
this project under the terms of its open-source license. The project credits the
|
|
||||||
open-source Fido script by Pete Batard for the underlying session flow.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,191 +0,0 @@
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { ArrowRight } from 'lucide-react'
|
|
||||||
import ProductCard from '../components/ProductCard'
|
|
||||||
import StatsBar from '../components/StatsBar'
|
|
||||||
import HowItWorks from '../components/HowItWorks'
|
|
||||||
import ComparisonTable from '../components/ComparisonTable'
|
|
||||||
import FAQAccordion from '../components/FAQAccordion'
|
|
||||||
|
|
||||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
|
||||||
|
|
||||||
const featured = [
|
|
||||||
{
|
|
||||||
id: '3262',
|
|
||||||
name: 'Windows 11',
|
|
||||||
version: '25H2',
|
|
||||||
build: '26200.6584',
|
|
||||||
description: 'The latest Windows 11 release with AI features and improved performance.',
|
|
||||||
badge: 'latest' as const,
|
|
||||||
archs: ['x64', 'ARM64'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3113',
|
|
||||||
name: 'Windows 11',
|
|
||||||
version: '24H2',
|
|
||||||
build: '26100.1742',
|
|
||||||
description: 'The widely deployed stable release. Recommended for enterprise environments.',
|
|
||||||
badge: 'stable' as const,
|
|
||||||
archs: ['x64', 'ARM64'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2618',
|
|
||||||
name: 'Windows 10',
|
|
||||||
version: '22H2',
|
|
||||||
build: '19045.2965',
|
|
||||||
description: 'The final Windows 10 feature update. Security support until October 2025.',
|
|
||||||
badge: 'eol' as const,
|
|
||||||
archs: ['x64', 'x86'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '52',
|
|
||||||
name: 'Windows 8.1',
|
|
||||||
version: 'RTM',
|
|
||||||
build: '9600.17415',
|
|
||||||
description: 'Legacy release for older hardware compatibility and historical reference.',
|
|
||||||
badge: 'legacy' as const,
|
|
||||||
archs: ['x64', 'x86'],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const container = {
|
|
||||||
hidden: {},
|
|
||||||
show: { transition: { staggerChildren: 0.08 } },
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardVariant = {
|
|
||||||
hidden: { opacity: 0, y: 20 },
|
|
||||||
show: { opacity: 1, y: 0, transition: { type: 'spring' as const, stiffness: 280, damping: 28 } },
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonLd = {
|
|
||||||
'@context': 'https://schema.org',
|
|
||||||
'@type': 'SoftwareApplication',
|
|
||||||
name: 'MSDL — Windows ISO Downloader',
|
|
||||||
applicationCategory: 'UtilitiesApplication',
|
|
||||||
operatingSystem: 'Web',
|
|
||||||
url: SITE_URL,
|
|
||||||
description: 'Download official Microsoft Windows ISO files for Windows 11, 10, and 8.1. Direct links from Microsoft CDN. Free, no registration.',
|
|
||||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomePage() {
|
|
||||||
const [totalReleases, setTotalReleases] = useState(16)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('/data/products.json')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then((data: Record<string, { active?: boolean }>) => {
|
|
||||||
const activeCount = Object.values(data).filter(p => p.active !== false).length
|
|
||||||
setTotalReleases(activeCount)
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<title>Windows ISO Downloader | Official Microsoft Images</title>
|
|
||||||
<meta name="description" content="Download official Microsoft Windows ISO files for Windows 11, 10, and 8.1. Fast, free, no registration required." />
|
|
||||||
<link rel="canonical" href={`${SITE_URL}/`} />
|
|
||||||
<meta property="og:title" content="Windows ISO Downloader | Official Microsoft Images" />
|
|
||||||
<meta property="og:description" content="Download official Microsoft Windows ISO files. Fast, free, no registration." />
|
|
||||||
<meta property="og:url" content={`${SITE_URL}/`} />
|
|
||||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto px-5 pt-20 pb-16 relative" style={{ overflowX: 'hidden' }}>
|
|
||||||
|
|
||||||
{/* Ambient glow — radial-gradient used over blur-3xl to optimize Mobile Lighthouse (Speed Index bypass) */}
|
|
||||||
<div
|
|
||||||
className="fixed top-0 left-1/2 -translate-x-1/2 w-[800px] h-[400px] pointer-events-none -z-10"
|
|
||||||
style={{ background: 'radial-gradient(ellipse at center, rgba(59,130,246,0.10) 0%, transparent 60%)' }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Hero */}
|
|
||||||
<motion.div
|
|
||||||
className="text-center mb-6"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
>
|
|
||||||
<p className="text-[11px] font-mono font-semibold tracking-[0.22em] uppercase text-blue-400 mb-5">
|
|
||||||
Official Microsoft ISOs
|
|
||||||
</p>
|
|
||||||
<h1
|
|
||||||
className="font-bold text-white tracking-tight leading-tight mb-4"
|
|
||||||
style={{ fontSize: 'clamp(1.6rem, 7.5vw, 3.75rem)' }}
|
|
||||||
>
|
|
||||||
Windows ISO <span className="text-white/50">Downloader</span>
|
|
||||||
</h1>
|
|
||||||
<p className="text-zinc-400 text-base max-w-sm mx-auto mb-6 leading-relaxed">
|
|
||||||
Direct links from Microsoft's CDN. No ads. No registration.
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center justify-center gap-4 text-[12px] text-zinc-500">
|
|
||||||
<span className="flex items-center gap-1.5">
|
|
||||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500 inline-block" />
|
|
||||||
Always official
|
|
||||||
</span>
|
|
||||||
<span className="text-zinc-700">·</span>
|
|
||||||
<span className="flex items-center gap-1.5">
|
|
||||||
<span className="w-1.5 h-1.5 rounded-full bg-blue-500 inline-block" />
|
|
||||||
Real-time links
|
|
||||||
</span>
|
|
||||||
<span className="text-zinc-700">·</span>
|
|
||||||
<span className="flex items-center gap-1.5">
|
|
||||||
<span className="w-1.5 h-1.5 rounded-full bg-zinc-600 inline-block" />
|
|
||||||
Free forever
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Stats bar */}
|
|
||||||
<div className="mb-10">
|
|
||||||
<StatsBar />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Featured grid */}
|
|
||||||
<motion.div
|
|
||||||
className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-8"
|
|
||||||
variants={container}
|
|
||||||
initial="hidden"
|
|
||||||
animate="show"
|
|
||||||
>
|
|
||||||
{featured.map(product => (
|
|
||||||
<motion.div key={product.id} variants={cardVariant}>
|
|
||||||
<Link to={`/product/${product.id}`} className="block outline-none">
|
|
||||||
<ProductCard
|
|
||||||
{...product}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Browse all CTA */}
|
|
||||||
<motion.div
|
|
||||||
className="text-center"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ delay: 0.5 }}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
to="/products"
|
|
||||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-white/8 bg-white/4 text-sm text-zinc-400 hover:text-white hover:border-white/15 hover:bg-white/7 transition-all duration-200"
|
|
||||||
>
|
|
||||||
Browse all {totalReleases} releases
|
|
||||||
<ArrowRight size={14} />
|
|
||||||
</Link>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* How it works */}
|
|
||||||
<HowItWorks />
|
|
||||||
|
|
||||||
{/* Comparison table */}
|
|
||||||
<ComparisonTable />
|
|
||||||
|
|
||||||
{/* FAQ */}
|
|
||||||
<FAQAccordion />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
|
|
||||||
export default function NotFoundPage() {
|
|
||||||
useEffect(() => { window.scrollTo(0, 0) }, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<title>404 Not Found | Windows ISO Downloader</title>
|
|
||||||
<meta name="description" content="The page you are looking for does not exist." />
|
|
||||||
<meta name="robots" content="noindex" />
|
|
||||||
<link rel="canonical" href="https://msdl.tech-latest.com/" />
|
|
||||||
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
|
|
||||||
<h1 className="text-6xl font-bold text-white mb-4">404</h1>
|
|
||||||
<p className="text-zinc-400 mb-8">The page you're looking for doesn't exist.</p>
|
|
||||||
<Link to="/" className="px-6 py-2 bg-white text-black font-semibold rounded-full hover:bg-zinc-200 transition-colors">
|
|
||||||
Go Home
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import { motion } from 'motion/react'
|
|
||||||
|
|
||||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
|
||||||
|
|
||||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h2 className="text-sm font-semibold text-white/80">{title}</h2>
|
|
||||||
<div className="text-sm text-zinc-500 leading-relaxed space-y-2">{children}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PrivacyPolicyPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<title>Privacy Policy | Windows ISO Downloader</title>
|
|
||||||
<meta name="description" content="Privacy policy for MSDL. We log no data, use no trackers, and proxy no files." />
|
|
||||||
<link rel="canonical" href={`${SITE_URL}/privacy-policy`} />
|
|
||||||
<meta property="og:title" content="Privacy Policy | Windows ISO Downloader" />
|
|
||||||
<meta property="og:description" content="Privacy policy for MSDL. We log no data, use no trackers, and proxy no files." />
|
|
||||||
<meta property="og:url" content={`${SITE_URL}/privacy-policy`} />
|
|
||||||
|
|
||||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 16 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.35 }}
|
|
||||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold text-white mb-1">Privacy Policy</h1>
|
|
||||||
<p className="text-xs font-mono text-zinc-600">Last updated: April 9, 2026</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Section title="No data collected">
|
|
||||||
<p>
|
|
||||||
MSDL does not collect, store, log, or share any personally identifiable information.
|
|
||||||
There are no user accounts, no cookies, no analytics trackers, and no third-party
|
|
||||||
advertising scripts of any kind.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="How download links work">
|
|
||||||
<p>
|
|
||||||
When you request a download link, our backend server contacts Microsoft's software
|
|
||||||
download API on your behalf using a non-Windows browser user-agent (the same method
|
|
||||||
used by open-source tools like Rufus/Fido). Microsoft's servers return a time-limited,
|
|
||||||
IP-tied signed URL.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Our backend does not log your IP address, does not store the generated link, and does
|
|
||||||
not proxy the actual file download — the ISO is downloaded directly from Microsoft's CDN.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Third-party services">
|
|
||||||
<p>
|
|
||||||
All Windows ISO files are hosted on Microsoft's official content delivery network
|
|
||||||
(<code className="text-zinc-400 text-[11px] font-mono bg-white/5 px-1 py-0.5 rounded">software.download.prss.microsoft.com</code>).
|
|
||||||
We are not responsible for Microsoft's privacy practices or link availability.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Open source">
|
|
||||||
<p>
|
|
||||||
The full source code for both the frontend and backend is publicly available.
|
|
||||||
You can inspect, audit, or self-host the entire stack.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Contact">
|
|
||||||
<p>
|
|
||||||
Questions? Reach us via{' '}
|
|
||||||
<a
|
|
||||||
href="https://tech-latest.com/contact-us/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
|
||||||
>
|
|
||||||
TechLatest
|
|
||||||
</a>.
|
|
||||||
</p>
|
|
||||||
</Section>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,507 +0,0 @@
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useParams, useNavigate, Link } from 'react-router-dom'
|
|
||||||
import { motion, AnimatePresence } from 'motion/react'
|
|
||||||
import * as Select from '@radix-ui/react-select'
|
|
||||||
import { ArrowLeft, ChevronDown, Download, AlertTriangle, Check, ExternalLink, WifiOff } from 'lucide-react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
import type { Sku, DownloadOption } from '../types'
|
|
||||||
import SystemRequirements from '../components/SystemRequirements'
|
|
||||||
import Aria2Tip from '../components/Aria2Tip'
|
|
||||||
import RelatedReleases from '../components/RelatedReleases'
|
|
||||||
|
|
||||||
const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:3002'
|
|
||||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
|
||||||
|
|
||||||
interface ProductCatalogEntry {
|
|
||||||
name: string
|
|
||||||
badge: string
|
|
||||||
archs: string[]
|
|
||||||
related: string[]
|
|
||||||
active?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function WinLogo({ className }: { className?: string }) {
|
|
||||||
return (
|
|
||||||
<svg viewBox="0 0 88 88" className={className} fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M0 12.402l35.687-4.86.016 34.423-35.67.203zm35.67 33.529l.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function archFromUri(uri: string): string {
|
|
||||||
const name = uri.split('/').pop()?.split('?')[0]?.toLowerCase() ?? ''
|
|
||||||
if (name.includes('arm')) return 'ARM64'
|
|
||||||
if (name.includes('x64') || name.includes('64')) return 'x64'
|
|
||||||
if (name.includes('x32') || name.includes('32') || name.includes('x86')) return 'x86'
|
|
||||||
return 'ISO'
|
|
||||||
}
|
|
||||||
|
|
||||||
function isWin11(productId: string): boolean {
|
|
||||||
const id = Number(productId)
|
|
||||||
return id >= 3113
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ProductDetailPage() {
|
|
||||||
const { productId } = useParams<{ productId: string }>()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const [productName, setProductName] = useState('')
|
|
||||||
const [buildStr, setBuildStr] = useState('')
|
|
||||||
const [languages, setLanguages] = useState<Sku[]>([])
|
|
||||||
const [selectedSku, setSelectedSku] = useState<Sku | null>(null)
|
|
||||||
const [downloadLinks, setDownloadLinks] = useState<DownloadOption[]>([])
|
|
||||||
const [isLoadingLangs, setIsLoadingLangs] = useState(true)
|
|
||||||
const [isFetching, setIsFetching] = useState(false)
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
|
||||||
const [copiedUri, setCopiedUri] = useState<string | null>(null)
|
|
||||||
const [isNotFound, setIsNotFound] = useState(false)
|
|
||||||
const [isValidated, setIsValidated] = useState(false)
|
|
||||||
const [hasCatalogError, setHasCatalogError] = useState(false)
|
|
||||||
|
|
||||||
const [meta, setMeta] = useState<{ badge: string; archs: string[]; active: boolean }>({ badge: '', archs: [], active: false })
|
|
||||||
const [related, setRelated] = useState<{ id: string; label: string }[]>([])
|
|
||||||
|
|
||||||
// Derived SEO values
|
|
||||||
const pageTitle = isNotFound
|
|
||||||
? 'Product Not Found | Windows ISO Downloader'
|
|
||||||
: hasCatalogError
|
|
||||||
? 'Error | Windows ISO Downloader'
|
|
||||||
: productName
|
|
||||||
? `${productName}${!meta.active ? ' (Discontinued)' : ''} ISO Download | Windows ISO Downloader`
|
|
||||||
: 'Loading... | Windows ISO Downloader'
|
|
||||||
|
|
||||||
const pageDescription = isNotFound
|
|
||||||
? 'The requested product could not be found.'
|
|
||||||
: hasCatalogError
|
|
||||||
? 'Failed to load product catalog.'
|
|
||||||
: productName
|
|
||||||
? `Download official ISO image for ${productName}. Direct links pulled securely from Microsoft CDN servers.`
|
|
||||||
: ''
|
|
||||||
|
|
||||||
const canonicalUrl = `${SITE_URL}/product/${productId}`
|
|
||||||
|
|
||||||
const breadcrumbJsonLd = productName && !isNotFound && !hasCatalogError ? {
|
|
||||||
'@context': 'https://schema.org',
|
|
||||||
'@type': 'BreadcrumbList',
|
|
||||||
itemListElement: [
|
|
||||||
{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL },
|
|
||||||
{ '@type': 'ListItem', position: 2, name: 'All Releases', item: `${SITE_URL}/products` },
|
|
||||||
{ '@type': 'ListItem', position: 3, name: productName, item: canonicalUrl },
|
|
||||||
],
|
|
||||||
} : null
|
|
||||||
|
|
||||||
// Load product name + build string
|
|
||||||
useEffect(() => {
|
|
||||||
setBuildStr('')
|
|
||||||
setIsNotFound(false)
|
|
||||||
setIsValidated(false)
|
|
||||||
setHasCatalogError(false)
|
|
||||||
setProductName('')
|
|
||||||
setMeta({ badge: '', archs: [], active: false })
|
|
||||||
setRelated([])
|
|
||||||
|
|
||||||
if (productId && !/^\d+$/.test(productId)) {
|
|
||||||
setIsNotFound(true)
|
|
||||||
setProductName('Invalid Product ID')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/data/products.json')
|
|
||||||
.then(r => {
|
|
||||||
if (!r.ok) throw new Error('Failed to fetch catalog')
|
|
||||||
return r.json()
|
|
||||||
})
|
|
||||||
.then((data: Record<string, ProductCatalogEntry>) => {
|
|
||||||
const product = data[productId!]
|
|
||||||
|
|
||||||
if (!product || !product.name) {
|
|
||||||
setIsNotFound(true)
|
|
||||||
setProductName('Product Not Found')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = product.name
|
|
||||||
setProductName(name)
|
|
||||||
const isActive = product.active !== false
|
|
||||||
setMeta({ badge: product.badge || '', archs: product.archs || [], active: isActive })
|
|
||||||
|
|
||||||
setRelated((product.related || []).map(rId => ({
|
|
||||||
id: rId,
|
|
||||||
label: data[rId]?.name || `Product ${rId}`
|
|
||||||
})))
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
setIsValidated(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract build from parentheses e.g. "(26200.6584)"
|
|
||||||
const match = name.match(/\(([^)]+)\)/)
|
|
||||||
if (match) setBuildStr(match[1])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setHasCatalogError(true)
|
|
||||||
setProductName('Error Loading Catalog')
|
|
||||||
})
|
|
||||||
}, [productId])
|
|
||||||
|
|
||||||
// Fetch languages
|
|
||||||
useEffect(() => {
|
|
||||||
if (!productId || !isValidated || !meta.active) return
|
|
||||||
setIsLoadingLangs(true)
|
|
||||||
setError(null)
|
|
||||||
setDownloadLinks([])
|
|
||||||
setSelectedSku(null)
|
|
||||||
|
|
||||||
fetch(`${API_BASE}/skuinfo?product_id=${productId}`)
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.error) throw new Error(data.error)
|
|
||||||
if (!data.Skus?.length) throw new Error('No languages found for this product.')
|
|
||||||
setLanguages(data.Skus)
|
|
||||||
const english = data.Skus.find((s: Sku) =>
|
|
||||||
s.LocalizedLanguage.toLowerCase().includes('english') &&
|
|
||||||
!s.LocalizedLanguage.toLowerCase().includes('international')
|
|
||||||
) ?? data.Skus[0]
|
|
||||||
setSelectedSku(english)
|
|
||||||
})
|
|
||||||
.catch(e => setError(e.message))
|
|
||||||
.finally(() => setIsLoadingLangs(false))
|
|
||||||
}, [productId, isValidated, meta.active])
|
|
||||||
|
|
||||||
async function handleGetLinks() {
|
|
||||||
if (!selectedSku || !productId) return
|
|
||||||
setIsFetching(true)
|
|
||||||
setError(null)
|
|
||||||
setDownloadLinks([])
|
|
||||||
|
|
||||||
fetch(`${API_BASE}/proxy?product_id=${productId}&sku_id=${selectedSku.Id}`)
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.error) throw new Error(data.error)
|
|
||||||
if (!data.ProductDownloadOptions?.length) throw new Error('No download links returned.')
|
|
||||||
setDownloadLinks(data.ProductDownloadOptions)
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
setError(e.message)
|
|
||||||
toast.error(e.message)
|
|
||||||
})
|
|
||||||
.finally(() => setIsFetching(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCopy(uri: string) {
|
|
||||||
navigator.clipboard.writeText(uri)
|
|
||||||
setCopiedUri(uri)
|
|
||||||
toast.success('Link copied to clipboard!')
|
|
||||||
setTimeout(() => setCopiedUri(null), 2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstUri = downloadLinks[0]?.Uri
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<title>{pageTitle}</title>
|
|
||||||
{pageDescription && <meta name="description" content={pageDescription} />}
|
|
||||||
<link rel="canonical" href={canonicalUrl} />
|
|
||||||
<meta property="og:title" content={pageTitle} />
|
|
||||||
{pageDescription && <meta property="og:description" content={pageDescription} />}
|
|
||||||
<meta property="og:url" content={canonicalUrl} />
|
|
||||||
{isNotFound && <meta name="robots" content="noindex" />}
|
|
||||||
{breadcrumbJsonLd && (
|
|
||||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="max-w-xl mx-auto px-5 pt-12 pb-10">
|
|
||||||
{/* Back */}
|
|
||||||
<button
|
|
||||||
onClick={() => navigate(window.history.state?.idx ? -1 : '/')}
|
|
||||||
className="flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-300 mb-8 transition-colors"
|
|
||||||
>
|
|
||||||
<ArrowLeft size={15} />
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 14 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.35 }}
|
|
||||||
>
|
|
||||||
{/* Product header */}
|
|
||||||
<div className="flex items-start gap-4 mb-8">
|
|
||||||
<div className="w-12 h-12 rounded-xl bg-white/5 border border-white/8 flex items-center justify-center flex-shrink-0">
|
|
||||||
<WinLogo className="w-6 h-6 text-white/50" />
|
|
||||||
</div>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<h1 className="text-2xl font-bold text-white leading-tight tracking-tight">
|
|
||||||
{productName || `Product ${productId}`}
|
|
||||||
</h1>
|
|
||||||
{buildStr && (
|
|
||||||
<p className="text-[12px] font-mono text-zinc-500 mt-0.5">Build {buildStr}</p>
|
|
||||||
)}
|
|
||||||
{meta.archs.length > 0 && (
|
|
||||||
<div className="flex gap-1.5 mt-2">
|
|
||||||
{meta.archs.map(arch => (
|
|
||||||
<span key={arch} className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded bg-white/5 border border-white/8 text-zinc-500">
|
|
||||||
{arch}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
{meta.badge && (
|
|
||||||
<span className="text-[10px] font-mono font-semibold px-2 py-0.5 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-400">
|
|
||||||
{meta.badge}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main card */}
|
|
||||||
<div className="rounded-2xl border border-white/7 bg-[#111113] p-6 space-y-5">
|
|
||||||
{!productName && !isNotFound && !hasCatalogError ? (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-2.5 text-xs text-zinc-400 font-medium tracking-wide">
|
|
||||||
<motion.span
|
|
||||||
animate={{ rotate: 360 }}
|
|
||||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
|
||||||
className="inline-block w-4 h-4 border-2 border-zinc-500/30 border-t-zinc-500 rounded-full"
|
|
||||||
/>
|
|
||||||
Loading catalog...
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="h-3 w-20 bg-white/6 rounded animate-pulse" />
|
|
||||||
<div className="h-11 bg-white/6 rounded-xl animate-pulse" />
|
|
||||||
<div className="h-11 bg-white/5 rounded-xl animate-pulse" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : hasCatalogError ? (
|
|
||||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
|
||||||
<WifiOff size={32} className="text-zinc-600 mb-4" />
|
|
||||||
<h2 className="text-lg font-semibold text-white mb-2">Network Error</h2>
|
|
||||||
<p className="text-zinc-500 text-sm max-w-xs px-4 leading-relaxed mb-6">
|
|
||||||
Failed to load the product catalog. Please check your internet connection or try refreshing the page.
|
|
||||||
</p>
|
|
||||||
<button onClick={() => window.location.reload()} className="px-4 py-2 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
|
||||||
Retry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : isNotFound ? (
|
|
||||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
|
||||||
<AlertTriangle size={32} className="text-zinc-600 mb-4" />
|
|
||||||
<h2 className="text-lg font-semibold text-white mb-2">Product Not Found</h2>
|
|
||||||
<p className="text-zinc-500 text-sm max-w-xs px-4 leading-relaxed mb-6">
|
|
||||||
The product ID you requested ({productId}) does not exist or has been removed from the catalog.
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center justify-center gap-3 w-full">
|
|
||||||
<Link to="/" className="flex-1 max-w-[140px] px-4 py-2.5 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
|
||||||
Home
|
|
||||||
</Link>
|
|
||||||
<Link to="/products" className="flex-1 max-w-[140px] px-4 py-2.5 rounded-xl bg-blue-500 text-sm font-medium text-white hover:bg-blue-600 transition-colors shadow-lg">
|
|
||||||
Browse all
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : !meta.active ? (
|
|
||||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
|
||||||
<AlertTriangle size={32} className="text-amber-500/50 mb-4" />
|
|
||||||
<h2 className="text-lg font-semibold text-white mb-2">Product Discontinued</h2>
|
|
||||||
<p className="text-zinc-500 text-sm max-w-sm px-4 leading-relaxed mb-6">
|
|
||||||
This release is no longer hosted on Microsoft's official CDN and cannot be downloaded.
|
|
||||||
</p>
|
|
||||||
<Link to="/products" className="px-4 py-2.5 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
|
||||||
Browse other releases
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
) : isLoadingLangs ? (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-2.5 text-xs text-zinc-400 font-medium tracking-wide">
|
|
||||||
<motion.span
|
|
||||||
animate={{ rotate: 360 }}
|
|
||||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
|
||||||
className="inline-block w-4 h-4 border-2 border-zinc-500/30 border-t-zinc-500 rounded-full"
|
|
||||||
/>
|
|
||||||
Connecting to Microsoft CDN...
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="h-3 w-20 bg-white/6 rounded animate-pulse" />
|
|
||||||
<div className="h-11 bg-white/6 rounded-xl animate-pulse" />
|
|
||||||
<div className="h-11 bg-white/5 rounded-xl animate-pulse" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : error && !languages.length ? (
|
|
||||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-red-500/8 border border-red-500/15 text-red-400 text-sm">
|
|
||||||
<AlertTriangle size={15} className="flex-shrink-0 mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p className="font-medium mb-0.5">Failed to load languages</p>
|
|
||||||
<p className="text-red-400/70 text-xs">{error}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{/* Language selector */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-2">
|
|
||||||
Language
|
|
||||||
</label>
|
|
||||||
<Select.Root
|
|
||||||
value={selectedSku?.Id}
|
|
||||||
onValueChange={val => {
|
|
||||||
const sku = languages.find(l => l.Id === val)
|
|
||||||
if (sku) setSelectedSku(sku)
|
|
||||||
setDownloadLinks([])
|
|
||||||
setError(null)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Select.Trigger className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-white/5 border border-white/7 text-sm text-white hover:bg-white/7 hover:border-white/12 focus:outline-none transition-all cursor-pointer">
|
|
||||||
<Select.Value placeholder="Select language..." />
|
|
||||||
<Select.Icon>
|
|
||||||
<ChevronDown size={14} className="text-zinc-500" />
|
|
||||||
</Select.Icon>
|
|
||||||
</Select.Trigger>
|
|
||||||
|
|
||||||
<Select.Portal>
|
|
||||||
<Select.Content
|
|
||||||
className="z-[100] w-[var(--radix-select-trigger-width)] max-h-72 overflow-auto rounded-xl border border-white/9 bg-[#131316] backdrop-blur-xl shadow-2xl text-sm"
|
|
||||||
position="popper"
|
|
||||||
sideOffset={6}
|
|
||||||
>
|
|
||||||
<Select.Viewport className="p-1">
|
|
||||||
{languages.map(lang => (
|
|
||||||
<Select.Item
|
|
||||||
key={lang.Id}
|
|
||||||
value={lang.Id}
|
|
||||||
className="relative flex items-center justify-between px-3 py-2.5 rounded-lg text-zinc-400 data-[highlighted]:text-white data-[highlighted]:bg-white/6 cursor-pointer outline-none transition-colors"
|
|
||||||
>
|
|
||||||
<Select.ItemText>{lang.LocalizedLanguage}</Select.ItemText>
|
|
||||||
<Select.ItemIndicator>
|
|
||||||
<Check size={13} className="text-blue-400" />
|
|
||||||
</Select.ItemIndicator>
|
|
||||||
</Select.Item>
|
|
||||||
))}
|
|
||||||
</Select.Viewport>
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Portal>
|
|
||||||
</Select.Root>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* CTA */}
|
|
||||||
<button
|
|
||||||
onClick={handleGetLinks}
|
|
||||||
disabled={!selectedSku || isFetching}
|
|
||||||
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-blue-500 text-white text-sm font-semibold hover:bg-blue-600 disabled:opacity-40 disabled:cursor-not-allowed transition-all shadow-[0_0_20px_rgba(59,130,246,0.2)] hover:shadow-[0_0_24px_rgba(59,130,246,0.3)]"
|
|
||||||
>
|
|
||||||
{isFetching ? (
|
|
||||||
<>
|
|
||||||
<motion.span
|
|
||||||
animate={{ rotate: 360 }}
|
|
||||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
|
||||||
className="inline-block w-4 h-4 border-2 border-white/30 border-t-white rounded-full"
|
|
||||||
/>
|
|
||||||
Fetching links…
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Download size={15} />
|
|
||||||
Get Download Links
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Error after fetch */}
|
|
||||||
<AnimatePresence>
|
|
||||||
{error && languages.length > 0 && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, height: 0 }}
|
|
||||||
animate={{ opacity: 1, height: 'auto' }}
|
|
||||||
exit={{ opacity: 0, height: 0 }}
|
|
||||||
className="flex items-start gap-3 p-4 rounded-xl bg-red-500/8 border border-red-500/15 text-red-400 text-sm"
|
|
||||||
>
|
|
||||||
<AlertTriangle size={15} className="flex-shrink-0 mt-0.5" />
|
|
||||||
<span>{error}</span>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{/* Download links */}
|
|
||||||
<AnimatePresence>
|
|
||||||
{downloadLinks.length > 0 && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, height: 0 }}
|
|
||||||
animate={{ opacity: 1, height: 'auto' }}
|
|
||||||
exit={{ opacity: 0, height: 0 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 28 }}
|
|
||||||
>
|
|
||||||
<div className="pt-4 border-t border-white/6 space-y-3">
|
|
||||||
{/* Warning */}
|
|
||||||
<div className="flex items-center gap-2 p-3 rounded-lg bg-amber-500/6 border border-amber-500/12 text-amber-400/80 text-[11px]">
|
|
||||||
<AlertTriangle size={12} className="flex-shrink-0" />
|
|
||||||
Links expire in 24 hours · IP-tied · Use a download manager for full speed
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Links */}
|
|
||||||
{downloadLinks.map(link => {
|
|
||||||
const filename = link.Uri.split('/').pop()?.split('?')[0] ?? 'download.iso'
|
|
||||||
const arch = link.Architecture || archFromUri(link.Uri)
|
|
||||||
const isCopied = copiedUri === link.Uri
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={link.Uri}
|
|
||||||
className="flex items-center justify-between gap-3 p-3.5 rounded-xl bg-white/3 border border-white/5 hover:border-white/9 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<p className="text-[12px] font-mono text-white truncate">{filename}</p>
|
|
||||||
<p className="text-[11px] text-zinc-600 mt-0.5">{arch}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
|
||||||
<button
|
|
||||||
onClick={() => handleCopy(link.Uri)}
|
|
||||||
className="p-2 rounded-lg text-zinc-600 hover:text-white hover:bg-white/6 transition-all"
|
|
||||||
title="Copy link"
|
|
||||||
>
|
|
||||||
{isCopied
|
|
||||||
? <Check size={13} className="text-green-400" />
|
|
||||||
: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} className="w-3.5 h-3.5" aria-hidden="true">
|
|
||||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
||||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
href={link.Uri}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-blue-500/12 border border-blue-500/20 text-blue-400 text-[11px] font-medium hover:bg-blue-500/20 transition-all"
|
|
||||||
>
|
|
||||||
<Download size={11} />
|
|
||||||
{arch}
|
|
||||||
<ExternalLink size={10} className="opacity-50" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(!isNotFound && !hasCatalogError) && (
|
|
||||||
<>
|
|
||||||
{/* System requirements */}
|
|
||||||
<SystemRequirements isWin11={isWin11(productId!)} />
|
|
||||||
|
|
||||||
{/* aria2 tip */}
|
|
||||||
{meta.active && <Aria2Tip downloadUrl={firstUri} />}
|
|
||||||
|
|
||||||
{/* Related releases */}
|
|
||||||
{related.length > 0 && (
|
|
||||||
<RelatedReleases current={productId!} items={related} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { Search, ArrowRight } from 'lucide-react'
|
|
||||||
import type { Product } from '../types'
|
|
||||||
|
|
||||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
|
||||||
|
|
||||||
const quickSearches = [
|
|
||||||
{ label: 'Windows 11 25H2', query: '25H2' },
|
|
||||||
{ label: 'Windows 11 24H2', query: '24H2' },
|
|
||||||
{ label: 'Windows 10', query: 'Windows 10' },
|
|
||||||
{ label: 'Windows 8.1', query: 'Windows 8.1' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function ProductsPage() {
|
|
||||||
const [products, setProducts] = useState<Product[]>([])
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
|
||||||
const [query, setQuery] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('/data/products.json')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then((data: Record<string, { name: string; active?: boolean }>) => {
|
|
||||||
const activeProducts = Object.entries(data)
|
|
||||||
.filter(([, product]) => product.active !== false)
|
|
||||||
.map(([id, product]) => ({ id, name: product.name }))
|
|
||||||
setProducts(activeProducts)
|
|
||||||
})
|
|
||||||
.finally(() => setIsLoading(false))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const filtered = products.filter(p =>
|
|
||||||
p.name.toLowerCase().includes(query.toLowerCase())
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<title>All Windows Releases | Windows ISO Downloader</title>
|
|
||||||
<meta name="description" content="Browse all official Microsoft Windows ISO releases available for direct download. Windows 11, 10, and 8.1." />
|
|
||||||
<link rel="canonical" href={`${SITE_URL}/products`} />
|
|
||||||
<meta property="og:title" content="All Windows ISO Releases | Windows ISO Downloader" />
|
|
||||||
<meta property="og:description" content="Browse all official Microsoft Windows ISO releases available for direct download." />
|
|
||||||
<meta property="og:url" content={`${SITE_URL}/products`} />
|
|
||||||
|
|
||||||
<div className="max-w-3xl mx-auto px-4 pt-12 pb-10">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 16 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.4 }}
|
|
||||||
>
|
|
||||||
<h1 className="text-3xl font-bold text-white mb-1">All Windows ISO Releases</h1>
|
|
||||||
<p className="text-white/40 text-sm mb-6">
|
|
||||||
{products.length} versions available
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Search */}
|
|
||||||
<div className="relative mb-4">
|
|
||||||
<Search size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-white/30" />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={query}
|
|
||||||
onChange={e => setQuery(e.target.value)}
|
|
||||||
placeholder="Search Windows versions..."
|
|
||||||
className="w-full bg-white/5 border border-white/8 rounded-xl pl-10 pr-4 py-3 text-sm text-white placeholder:text-white/25 focus:outline-none focus:border-white/20 focus:bg-white/8 transition-all"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Quick filters */}
|
|
||||||
<div className="flex flex-wrap gap-2 mb-6">
|
|
||||||
{quickSearches.map(s => (
|
|
||||||
<button
|
|
||||||
key={s.label}
|
|
||||||
onClick={() => setQuery(s.query)}
|
|
||||||
className="px-3 py-1.5 rounded-lg text-xs font-medium border border-white/8 bg-white/4 text-white/50 hover:text-white hover:border-white/15 hover:bg-white/8 transition-all"
|
|
||||||
>
|
|
||||||
{s.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
{query && (
|
|
||||||
<button
|
|
||||||
onClick={() => setQuery('')}
|
|
||||||
className="px-3 py-1.5 rounded-lg text-xs font-medium border border-white/8 bg-white/4 text-white/40 hover:text-white transition-all"
|
|
||||||
>
|
|
||||||
Clear ×
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* List */}
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{[...Array(8)].map((_, i) => (
|
|
||||||
<div key={i} className="h-14 rounded-xl bg-white/4 animate-pulse" />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : filtered.length === 0 ? (
|
|
||||||
<p className="text-white/30 text-sm text-center py-12">No products found.</p>
|
|
||||||
) : (
|
|
||||||
<motion.div
|
|
||||||
className="space-y-1.5"
|
|
||||||
initial="hidden"
|
|
||||||
animate="show"
|
|
||||||
variants={{ show: { transition: { staggerChildren: 0.03 } } }}
|
|
||||||
>
|
|
||||||
{filtered.map(product => (
|
|
||||||
<motion.div
|
|
||||||
key={product.id}
|
|
||||||
variants={{
|
|
||||||
hidden: { opacity: 0, x: -8 },
|
|
||||||
show: { opacity: 1, x: 0, transition: { type: 'spring', stiffness: 300, damping: 28 } },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
to={`/product/${product.id}`}
|
|
||||||
className="group w-full flex items-center justify-between px-4 py-3.5 rounded-xl border border-white/6 bg-white/3 hover:bg-white/6 hover:border-white/12 transition-all text-left"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<svg viewBox="0 0 88 88" className="w-5 h-5 text-white/30 flex-shrink-0 group-hover:text-white/50 transition-colors" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M0 12.402l35.687-4.86.016 34.423-35.67.203zm35.67 33.529l.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z" />
|
|
||||||
</svg>
|
|
||||||
<span className="text-sm text-white/70 group-hover:text-white transition-colors font-medium">
|
|
||||||
{product.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ArrowRight size={14} className="text-white/20 group-hover:text-white/50 group-hover:translate-x-0.5 transition-all" />
|
|
||||||
</Link>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
export interface Sku {
|
|
||||||
Id: string
|
|
||||||
Language: string
|
|
||||||
LocalizedLanguage: string
|
|
||||||
FriendlyFileNames?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SkuInfoResponse {
|
|
||||||
Skus: Sku[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DownloadOption {
|
|
||||||
Uri: string
|
|
||||||
Architecture: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProxyResponse {
|
|
||||||
ProductDownloadOptions: DownloadOption[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Product {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
"target": "ES2023",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"types": ["vite/client"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"erasableSyntaxOnly": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"files": [],
|
|
||||||
"references": [
|
|
||||||
{ "path": "./tsconfig.app.json" },
|
|
||||||
{ "path": "./tsconfig.node.json" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
||||||
"target": "ES2023",
|
|
||||||
"lib": ["ES2023"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"types": ["node"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"erasableSyntaxOnly": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import react from '@vitejs/plugin-react'
|
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
react(),
|
|
||||||
tailwindcss(),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
# Issue Drafts
|
|
||||||
|
|
||||||
## Inactive products still trigger backend requests
|
|
||||||
|
|
||||||
### Summary
|
|
||||||
|
|
||||||
The new `active: false` catalog state is not fully enforced on the product detail page.
|
|
||||||
|
|
||||||
Inactive products still pass validation and trigger `/skuinfo` requests before the UI shows the discontinued-product panel.
|
|
||||||
|
|
||||||
### Affected file
|
|
||||||
|
|
||||||
- `frontend/src/pages/ProductDetailPage.tsx`
|
|
||||||
|
|
||||||
### Current behavior
|
|
||||||
|
|
||||||
For a catalog entry with `active: false`:
|
|
||||||
|
|
||||||
- the page loads the product from `products.json`
|
|
||||||
- `setIsValidated(true)` still runs
|
|
||||||
- the follow-up effect calls `/skuinfo?product_id=...`
|
|
||||||
- the backend and Microsoft endpoints are still hit
|
|
||||||
- only afterward does the page render the `Product Discontinued` state
|
|
||||||
|
|
||||||
### Expected behavior
|
|
||||||
|
|
||||||
Inactive products should fail fast on the frontend and should not trigger backend or Microsoft API requests.
|
|
||||||
|
|
||||||
### Risk
|
|
||||||
|
|
||||||
The inactive-product flow still consumes backend capacity and external request budget even though the user cannot download that release.
|
|
||||||
|
|
||||||
### Suggested fix
|
|
||||||
|
|
||||||
- gate validation on `product.active !== false`
|
|
||||||
- avoid setting `isValidated` for inactive products
|
|
||||||
- ensure the `/skuinfo` effect only runs for active products
|
|
||||||
|
|
||||||
### Prevention
|
|
||||||
|
|
||||||
- add a test covering an inactive catalog entry and assert that no backend request is made
|
|
||||||
- model the route state explicitly so `active`, `not_found`, and `error` paths cannot fall through into fetch logic
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import uuid
|
|
||||||
import time
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# New v3 JSON Endpoints
|
|
||||||
BASE_URL = "https://www.microsoft.com/software-download-connector/api"
|
|
||||||
PROFILE = "606624d44113"
|
|
||||||
LOCALE = "en-US"
|
|
||||||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
||||||
|
|
||||||
def setup_session():
|
|
||||||
"""Initializes a session with Microsoft tracking servers."""
|
|
||||||
session_id = str(uuid.uuid4())
|
|
||||||
return session_id
|
|
||||||
|
|
||||||
def get_product(product_id, session_id):
|
|
||||||
"""Fetches product/SKU info for a given ID."""
|
|
||||||
url = f"{BASE_URL}/getskuinformationbyproductedition"
|
|
||||||
params = {
|
|
||||||
"profile": PROFILE,
|
|
||||||
"productEditionId": product_id,
|
|
||||||
"SKU": "undefined",
|
|
||||||
"friendlyFileName": "undefined",
|
|
||||||
"Locale": LOCALE,
|
|
||||||
"sessionID": session_id
|
|
||||||
}
|
|
||||||
headers = {
|
|
||||||
"User-Agent": UA,
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Referer": "https://www.microsoft.com/en-us/software-download/windows11"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = requests.get(url, params=params, headers=headers, timeout=10)
|
|
||||||
if not r.ok: return None
|
|
||||||
|
|
||||||
# Handle Microsoft's double-encoded JSON
|
|
||||||
raw_text = r.text
|
|
||||||
try:
|
|
||||||
# First pass: decode the wrapper JSON
|
|
||||||
data = json.loads(raw_text)
|
|
||||||
# Second pass: if the value is a string, it's the real JSON
|
|
||||||
if isinstance(data, str):
|
|
||||||
data = json.loads(data)
|
|
||||||
return data
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error fetching ID {product_id}: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def scan_id(product_id):
|
|
||||||
"""Checks if a product ID is valid and active."""
|
|
||||||
session_id = setup_session()
|
|
||||||
data = get_product(product_id, session_id)
|
|
||||||
|
|
||||||
if not data or "Skus" not in data or len(data["Skus"]) == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if "Errors" in data and data["Errors"] and len(data["Errors"]) > 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# MS usually puts the release name in the first SKU
|
|
||||||
skus = data.get("Skus", [])
|
|
||||||
if skus:
|
|
||||||
# Check multiple possible name keys
|
|
||||||
s = skus[0]
|
|
||||||
# ReleaseName is often "Windows 10 Version 22H2 (Updated Oct 2025)"
|
|
||||||
name = s.get("EditionName") or s.get("ReleaseName") or s.get("FriendlyName")
|
|
||||||
if name:
|
|
||||||
return name
|
|
||||||
|
|
||||||
return f"Windows Product {product_id}"
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(description="Scan for new Windows Product IDs (v3 JSON API)")
|
|
||||||
parser.add_argument("--first", required=True, type=int, help="First ID to check")
|
|
||||||
parser.add_argument("--last", required=True, type=int, help="Last ID to check")
|
|
||||||
parser.add_argument("--write", help="Output JSON file")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
|
||||||
products = {}
|
|
||||||
|
|
||||||
logging.info(f"Scanning range {args.first} to {args.last}...")
|
|
||||||
|
|
||||||
for i in range(args.first, args.last + 1):
|
|
||||||
name = scan_id(i)
|
|
||||||
if name:
|
|
||||||
logging.info(f"✅ FOUND: [{i}] {name}")
|
|
||||||
products[str(i)] = name
|
|
||||||
else:
|
|
||||||
# Only log skips for very small ranges to keep output clean
|
|
||||||
if (args.last - args.first) < 20:
|
|
||||||
logging.info(f"❌ SKIP: [{i}] Not found")
|
|
||||||
products[str(i)] = False
|
|
||||||
|
|
||||||
# Be gentle to the API
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
if args.write:
|
|
||||||
import os
|
|
||||||
catalog = {}
|
|
||||||
if os.path.exists(args.write):
|
|
||||||
try:
|
|
||||||
with open(args.write, 'r', encoding='utf-8') as f:
|
|
||||||
catalog = json.load(f)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Failed to read existing catalog: {e}")
|
|
||||||
|
|
||||||
# Merge new products, preserving existing extra metadata if present
|
|
||||||
for pid, name in products.items():
|
|
||||||
if pid in catalog:
|
|
||||||
if name is False:
|
|
||||||
if isinstance(catalog[pid], dict):
|
|
||||||
catalog[pid]["active"] = False
|
|
||||||
else:
|
|
||||||
catalog[pid] = {
|
|
||||||
"name": catalog[pid],
|
|
||||||
"badge": "",
|
|
||||||
"archs": [],
|
|
||||||
"related": [],
|
|
||||||
"active": False
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Update name but keep other fields like badge, archs, etc.
|
|
||||||
if isinstance(catalog[pid], dict):
|
|
||||||
catalog[pid]["name"] = name
|
|
||||||
catalog[pid]["active"] = True
|
|
||||||
else:
|
|
||||||
# Upgrade from old flat format
|
|
||||||
catalog[pid] = {
|
|
||||||
"name": name,
|
|
||||||
"badge": "",
|
|
||||||
"archs": [],
|
|
||||||
"related": [],
|
|
||||||
"active": True
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
if name is False:
|
|
||||||
continue # Do not add new dead entries to the catalog
|
|
||||||
catalog[pid] = {
|
|
||||||
"name": name,
|
|
||||||
"badge": "",
|
|
||||||
"archs": [],
|
|
||||||
"related": [],
|
|
||||||
"active": True
|
|
||||||
}
|
|
||||||
|
|
||||||
with open(args.write, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(catalog, f, indent=4)
|
|
||||||
logging.info(f"\nSaved {len(catalog)} products to {args.write} (merged with existing)")
|
|
||||||
|
|
||||||
logging.info("Done.")
|
|
||||||
3
.gitignore
vendored
|
|
@ -51,3 +51,6 @@ logs/
|
||||||
.cache/
|
.cache/
|
||||||
.temp/
|
.temp/
|
||||||
tmp/
|
tmp/
|
||||||
|
|
||||||
|
# ── Claude Code internal ───────────────────────────────
|
||||||
|
.claude/
|
||||||
|
|
|
||||||