docs: record redesign review artifacts
This commit is contained in:
38
tasks/concepts/a-campus-wayfinding.html
Normal file
38
tasks/concepts/a-campus-wayfinding.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fa" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="./assets/favicon.ico">
|
||||||
|
<title>مسیرهای انجمن — طرح A</title>
|
||||||
|
<style>
|
||||||
|
@font-face{font-family:Vazir;src:url('./assets/Vazirmatn-variable.woff2') format('woff2');font-display:swap}
|
||||||
|
:root{--paper:#f2f6f2;--ink:#103d3b;--green:#226d64;--mint:#c9e2d9;--white:#fff;--signal:#ad3c20;--line:#9cb9af}
|
||||||
|
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--paper);color:var(--ink);font:400 16px/1.8 Vazir,Tahoma,sans-serif}
|
||||||
|
a{color:inherit}a:focus-visible{outline:3px solid var(--signal);outline-offset:4px}::selection{background:var(--mint);color:var(--ink)}
|
||||||
|
.wrap{width:min(100% - 48px,1200px);margin:auto}.mast{border-bottom:1px solid var(--line);background:var(--white)}
|
||||||
|
.mast .wrap{min-height:94px;display:flex;justify-content:space-between;align-items:center;gap:24px}
|
||||||
|
.brand{display:flex;align-items:center;gap:14px;text-decoration:none;min-width:0}.brand img{width:56px;height:56px;object-fit:contain}.brand strong{display:block;font-size:18px;line-height:1.45}.brand small{display:block;color:var(--green);font-size:12px}
|
||||||
|
nav{display:flex;align-items:center;gap:8px;flex-wrap:wrap}nav a{text-decoration:none;font-weight:650;padding:8px 13px;border-radius:4px}nav a:hover{background:var(--mint)}nav .active,nav .active:hover{color:var(--white);background:var(--ink)}
|
||||||
|
.hero{position:relative;overflow:hidden;border-bottom:1px solid var(--line)}.hero:before{content:"";position:absolute;right:max(24px,calc((100% - 1200px)/2));top:0;bottom:0;width:2px;background:var(--signal)}
|
||||||
|
.hero .wrap{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(300px,.75fr);gap:7%;align-items:center;min-height:610px;padding-block:80px}
|
||||||
|
.hero-copy{padding-right:36px;position:relative}.hero-copy:before{content:"";position:absolute;right:-9px;top:31px;width:20px;height:20px;border-radius:50%;background:var(--signal);border:4px solid var(--paper)}
|
||||||
|
h1{font-size:clamp(36px,5.2vw,76px);font-weight:850;line-height:1.25;letter-spacing:-.02em;margin:0 0 24px;max-width:12ch}h1 em{font-style:normal;color:var(--green)}
|
||||||
|
.lead{font-size:clamp(17px,1.6vw,21px);max-width:45ch;margin:0 0 30px}.actions{display:flex;gap:10px;flex-wrap:wrap}.button{display:inline-flex;align-items:center;justify-content:center;min-height:50px;padding:9px 24px;border:2px solid var(--ink);text-decoration:none;font-weight:700;border-radius:4px}.button.primary{background:var(--ink);color:var(--white)}.button:hover{background:var(--signal);border-color:var(--signal);color:var(--white)}
|
||||||
|
.identity-panel{background:var(--ink);color:var(--white);padding:34px 32px;min-height:400px;display:flex;flex-direction:column;justify-content:space-between;position:relative}.identity-panel:after{content:"";position:absolute;left:22px;top:22px;width:20px;height:20px;border:2px solid var(--mint);border-radius:50%}.identity-panel span{color:var(--mint);font-size:13px}.identity-panel p{font-weight:700;font-size:clamp(21px,2.1vw,28px);line-height:1.65;margin:16px 0}.identity-panel .faculty{border-top:1px solid #4c7770;padding-top:18px;font-size:15px}
|
||||||
|
.paths{padding-block:72px}.paths h2,.about h2{font-size:clamp(28px,3vw,42px);line-height:1.4;margin:0 0 26px}.paths .route{display:grid;grid-template-columns:80px 1fr auto;align-items:center;gap:22px;padding:22px 12px;border-top:1px solid var(--line);text-decoration:none}.paths .route:last-child{border-bottom:1px solid var(--line)}.route .mark{display:grid;place-items:center;width:48px;height:48px;background:var(--mint)}.route .mark:before{content:"";width:12px;height:12px;border:3px solid var(--green);border-radius:50%}.route strong{font-size:22px}.route small{display:block;color:#345c55;font-size:14px}.route .arrow{width:14px;height:14px;border-left:2px solid var(--signal);border-bottom:2px solid var(--signal);transform:rotate(45deg)}.route:hover{background:#e3eee8}
|
||||||
|
.about{background:var(--white);border-top:1px solid var(--line)}.about .wrap{display:grid;grid-template-columns:1fr 1fr;gap:8%;padding-block:92px}.about .intro{position:sticky;top:40px;align-self:start}.about .intro p{font-size:20px;max-width:34ch}.about .intro img{width:74px;height:74px;object-fit:contain;margin-top:24px}.about .details{border-right:2px solid var(--signal);padding-right:34px}.detail{padding:4px 0 24px;border-bottom:1px solid var(--line);margin-bottom:25px}.detail h3{font-size:20px;margin:0 0 7px}.detail p{margin:0;color:#345c55}.contact a{display:block;overflow-wrap:anywhere;text-decoration-thickness:1px;text-underline-offset:4px}
|
||||||
|
footer{background:var(--ink);color:var(--white);padding:36px 0}footer .wrap{display:flex;justify-content:space-between;gap:20px;align-items:center}footer p{margin:0}footer a{color:var(--mint)}
|
||||||
|
@media(max-width:760px){.wrap{width:min(100% - 32px,1200px)}.mast .wrap{min-height:76px}.brand img{width:42px;height:42px}.brand strong{font-size:13px}.brand small{font-size:11px}nav{display:none}.hero:before{right:16px}.hero .wrap{grid-template-columns:1fr;gap:34px;min-height:0;padding-block:54px}.hero-copy{padding-right:25px}h1{max-width:14ch}.identity-panel{min-height:260px}.paths{padding-block:50px}.paths .route{grid-template-columns:40px 1fr 18px;gap:12px;padding:17px 2px}.route .mark{width:38px;height:38px;font-size:19px}.route strong{font-size:17px}.route small{font-size:12px}.about .wrap{grid-template-columns:1fr;padding-block:55px;gap:26px}.about .intro{position:static}.about .details{padding-right:20px}footer .wrap{display:block}footer p+ p{margin-top:10px}}
|
||||||
|
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="mast"><div class="wrap"><a class="brand" href="#top"><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"><span><strong>انجمن علمی مهندسی کامپیوتر</strong><small>دانشکدهی فنی و مهندسی شرق گیلان</small></span></a><nav aria-label="ناوبری طرح"><a class="active" href="#top">خانه</a><a href="#about">درباره انجمن</a><a href="#paths">مسیرها</a></nav></div></header>
|
||||||
|
<main id="top">
|
||||||
|
<section class="hero" aria-labelledby="hero-title"><div class="wrap"><div class="hero-copy"><h1 id="hero-title">جایی برای <em>یادگیری و همکاری</em> دانشجویان کامپیوتر</h1><p class="lead">انجمن علمی مهندسی کامپیوتر دانشگاه گیلان در دانشکدهی فنی و مهندسی شرق گیلان فعالیت میکند. اینجا میتوانید با فعالیتهای انجمن آشنا شوید و راه مشارکت خود را پیدا کنید.</p><div class="actions"><a class="button primary" href="#about">آشنایی با انجمن</a><a class="button" href="#paths">دیدن مسیرها</a></div></div><div class="identity-panel"><span>هویت انجمن</span><p>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</p><div class="faculty">دانشکدهی فنی و مهندسی شرق گیلان</div></div></div></section>
|
||||||
|
<section id="paths" class="paths wrap" aria-labelledby="paths-title"><h2 id="paths-title">از کجا شروع کنم؟</h2><a class="route" href="#about"><span class="mark" aria-hidden="true"></span><span><strong>آشنایی با انجمن</strong><small>مأموریت و راههای ارتباطی</small></span><span class="arrow" aria-hidden="true"></span></a><a class="route" href="https://east-guilan-ce.ir/events"><span class="mark" aria-hidden="true"></span><span><strong>رویدادها</strong><small>برنامهها و فرصتهای یادگیری</small></span><span class="arrow" aria-hidden="true"></span></a><a class="route" href="https://east-guilan-ce.ir/blog"><span class="mark" aria-hidden="true"></span><span><strong>بلاگ</strong><small>نوشتهها و محتوای آموزشی</small></span><span class="arrow" aria-hidden="true"></span></a></section>
|
||||||
|
<section id="about" class="about" aria-labelledby="about-title"><div class="wrap"><div class="intro"><h2 id="about-title">درباره انجمن</h2><p>یادگیری پیوسته، اشتراک دانش و فرصت برابر برای مشارکت، محور فعالیت انجمن است.</p><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"></div><div class="details"><div class="detail"><h3>مأموریت</h3><p>توانمندسازی دانشجویان با مهارتهای موردنیاز صنعت، ایجاد شبکههای حرفهای و فراهمکردن مسیرهای یادگیری پروژهمحور.</p></div><div class="detail"><h3>فعالیتها</h3><p>برگزاری رویدادها، کارگاهها و نشستهای تخصصی و انتشار محتوای آموزشی.</p></div><div class="detail contact"><h3>ارتباط با انجمن</h3><a href="mailto:eastguilanceassociation@gmail.com" dir="ltr">eastguilanceassociation@gmail.com</a><a href="https://t.me/guilancea" dir="ltr">t.me/guilancea</a><a href="https://instagram.com/guilance.ir" dir="ltr">instagram.com/guilance.ir</a></div></div></div></section>
|
||||||
|
</main><footer><div class="wrap"><p>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</p><p><a href="#top">بازگشت به آغاز</a></p></div></footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
32
tasks/concepts/b-association-journal.html
Normal file
32
tasks/concepts/b-association-journal.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fa" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="./assets/favicon.ico">
|
||||||
|
<title>دفتر انجمن — طرح B</title>
|
||||||
|
<style>
|
||||||
|
@font-face{font-family:Vazir;src:url('./assets/Vazirmatn-variable.woff2') format('woff2');font-display:swap}
|
||||||
|
:root{--navy:#14243f;--blue:#42649a;--paper:#fff9ea;--cream:#f1e8d0;--gold:#f0be54;--white:#fff;--line:#b8b2a4}
|
||||||
|
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--paper);color:var(--navy);font:400 16px/1.85 Vazir,Tahoma,sans-serif}a{color:inherit}a:focus-visible{outline:3px solid var(--gold);outline-offset:4px}::selection{background:var(--gold);color:var(--navy)}
|
||||||
|
.wrap{width:min(100% - 56px,1240px);margin:auto}.header{background:var(--navy);color:var(--white);border-bottom:1px solid #66758e}.header .wrap{display:flex;justify-content:space-between;align-items:center;gap:30px;min-height:92px}.brand{display:flex;align-items:center;gap:13px;text-decoration:none}.brand img{height:52px;width:52px;object-fit:contain}.brand span{font-size:14px;font-weight:750;max-width:250px;line-height:1.5}.header nav{display:flex;gap:26px}.header nav a{color:#ecf0f7;text-decoration:none;text-underline-offset:9px;font-size:14px}.header nav a:hover,.header nav a:focus-visible{text-decoration:underline}
|
||||||
|
.cover{background:var(--navy);color:var(--paper);overflow:hidden}.cover .wrap{padding-block:70px 90px;display:grid;grid-template-columns:1.3fr .7fr;gap:8%;min-height:620px;align-items:end}.headline{position:relative}h1{font-weight:900;font-size:clamp(48px,7.3vw,112px);letter-spacing:-.035em;line-height:1.16;margin:0;max-width:12ch}h1 span{display:block;color:var(--gold)}.headline:after{content:"";display:block;width:70%;height:1px;background:#8190a3;margin-top:36px}.cover-note{align-self:center;border-top:2px solid var(--gold);background:var(--paper);color:var(--navy);padding:28px 30px 32px}.cover-note p{font-size:19px;line-height:1.8;margin:0 0 24px}.cover-note a{display:inline-block;font-weight:750;text-underline-offset:5px}.cover-note strong{display:block;font-size:13px;color:#49628b;margin-bottom:18px}
|
||||||
|
.ribbon{background:var(--gold);padding:16px 0}.ribbon .wrap{display:flex;align-items:center;gap:32px;flex-wrap:wrap;font-weight:750}.ribbon span{margin-left:auto}.ribbon a{text-decoration:none;border-bottom:1px solid currentColor}.ribbon a:hover{text-decoration:underline}
|
||||||
|
.chapter{padding-block:88px 100px}.chapter-head{display:grid;grid-template-columns:1fr 1fr;gap:7%;padding-bottom:40px;border-bottom:2px solid var(--navy)}.chapter h2{font-size:clamp(36px,4.2vw,62px);line-height:1.25;margin:0}.chapter-head p{font-size:21px;line-height:1.8;margin:0}.ledger{display:grid;grid-template-columns:repeat(3,1fr);border-bottom:1px solid var(--line)}.ledger div{padding:30px 22px 32px;border-left:1px solid var(--line)}.ledger div:first-child{padding-right:0}.ledger div:last-child{border:0}.ledger h3{font-size:23px;margin:0 0 10px}.ledger p{margin:0;color:#384c69}
|
||||||
|
.about{background:var(--cream);padding-block:92px}.about .wrap{display:grid;grid-template-columns:.8fr 1.2fr;gap:9%}.about aside{border-top:2px solid var(--navy);padding-top:22px}.about aside img{width:86px;height:86px;object-fit:contain}.about h2{font-size:clamp(38px,5vw,70px);line-height:1.2;margin:15px 0 26px}.about .name{font-weight:760;font-size:20px;line-height:1.7}.about article{padding-top:34px}.about article p{font-size:20px;line-height:2;max-width:44ch;margin:0 0 32px}.about dl{border-top:1px solid var(--navy);margin:0}.about dl div{display:grid;grid-template-columns:130px 1fr;border-bottom:1px solid var(--line);gap:20px;padding:18px 0}.about dt{font-weight:800}.about dd{margin:0;overflow-wrap:anywhere}.about dd a{text-underline-offset:4px}
|
||||||
|
.end{background:var(--navy);color:var(--paper);padding:58px 0}.end .wrap{display:flex;justify-content:space-between;align-items:end;gap:30px}.end p{margin:0;font-weight:700;font-size:22px;max-width:30ch}.end a{color:var(--gold);white-space:nowrap}
|
||||||
|
@media(max-width:820px){.wrap{width:min(100% - 32px,1240px)}.header .wrap{min-height:75px}.brand img{width:42px;height:42px}.brand span{font-size:12px}.header nav{display:none}.cover .wrap{grid-template-columns:1fr;gap:52px;min-height:0;padding-block:52px 68px}.cover-note{max-width:550px}.ribbon .wrap{gap:12px}.ribbon span{width:100%}.chapter{padding-block:58px}.chapter-head{grid-template-columns:1fr;gap:17px}.chapter-head p{font-size:18px}.ledger{grid-template-columns:1fr}.ledger div,.ledger div:first-child{padding:23px 0;border-left:0;border-bottom:1px solid var(--line)}.ledger div:last-child{border:0}.about{padding-block:60px}.about .wrap{grid-template-columns:1fr;gap:20px}.about article{padding-top:0}.about article p{font-size:18px}.end .wrap{display:block}.end a{display:inline-block;margin-top:22px}}
|
||||||
|
@media(max-width:400px){h1{font-size:45px}.about dl div{grid-template-columns:1fr;gap:3px}.cover-note{padding:22px}}
|
||||||
|
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header"><div class="wrap"><a class="brand" href="#top"><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"><span>انجمن علمی مهندسی کامپیوتر<br>دانشکدهی فنی و مهندسی شرق گیلان</span></a><nav aria-label="ناوبری طرح"><a href="#top">آغاز</a><a href="#chapter">فعالیتها</a><a href="#about">درباره انجمن</a></nav></div></header>
|
||||||
|
<main id="top">
|
||||||
|
<section class="cover" aria-labelledby="cover-title"><div class="wrap"><div class="headline"><h1 id="cover-title">انجمن برای <span>یاد گرفتن با هم</span></h1></div><div class="cover-note"><strong>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</strong><p>در دانشکدهی فنی و مهندسی شرق گیلان، فعالیت انجمن فرصتی برای یادگیری، اشتراک دانش و مشارکت دانشجویان است.</p><a href="#about">انجمن را بشناسید</a></div></div></section>
|
||||||
|
<div class="ribbon"><div class="wrap"><span>برای آشنایی با فعالیتها</span><a href="#chapter">برنامههای انجمن</a><a href="#about">مأموریت و ارتباط</a></div></div>
|
||||||
|
<section id="chapter" class="chapter wrap" aria-labelledby="chapter-title"><div class="chapter-head"><h2 id="chapter-title">فعالیتهای انجمن</h2><p>از رویدادهای تخصصی تا نوشتههای آموزشی، برنامهها برای یادگیری و ارتباط میان دانشجویان شکل میگیرند.</p></div><div class="ledger"><div><h3>رویدادها</h3><p>کارگاهها، سمینارها و نشستهای علمی.</p></div><div><h3>محتوا</h3><p>مقالات آموزشی و یادداشتهای تخصصی.</p></div><div><h3>همکاری</h3><p>مشارکت دانشجویان و اشتراک تجربه.</p></div></div></section>
|
||||||
|
<section id="about" class="about" aria-labelledby="about-title"><div class="wrap"><aside><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"><h2 id="about-title">درباره انجمن</h2><p class="name">انجمن علمی مهندسی کامپیوتر دانشگاه گیلان (دانشکدهی فنی و مهندسی شرق گیلان)</p></aside><article><p>مأموریت انجمن، توانمندسازی دانشجویان با مهارتهای موردنیاز صنعت، ایجاد شبکههای حرفهای و فراهمکردن مسیرهای یادگیری پروژهمحور است.</p><dl><div><dt>ایمیل</dt><dd><a dir="ltr" href="mailto:eastguilanceassociation@gmail.com">eastguilanceassociation@gmail.com</a></dd></div><div><dt>تلگرام</dt><dd><a dir="ltr" href="https://t.me/guilancea">t.me/guilancea</a></dd></div><div><dt>اینستاگرام</dt><dd><a dir="ltr" href="https://instagram.com/guilance.ir">instagram.com/guilance.ir</a></dd></div></dl></article></div></section>
|
||||||
|
</main><footer class="end"><div class="wrap"><p>یادگیری پیوسته و اشتراک دانش، از ارزشهای انجمن است.</p><a href="#top">بازگشت به آغاز</a></div></footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
tasks/concepts/c-current-refinement.html
Normal file
52
tasks/concepts/c-current-refinement.html
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fa" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="./assets/favicon.ico">
|
||||||
|
<title>پالایش طرح فعلی — خانه و درباره انجمن</title>
|
||||||
|
<style>
|
||||||
|
@font-face{font-family:Vazirmatn;src:url('./assets/Vazirmatn-variable.woff2') format('woff2');font-display:swap}
|
||||||
|
:root{--background:#fff;--ink:#0f172a;--muted:#475569;--line:#e2e8f0;--wash:#f4f8fc;--accent:#1e4f85;--focus:#2563eb}
|
||||||
|
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--background);color:var(--ink);font:400 16px/1.8 Vazirmatn,Tahoma,sans-serif}a{color:inherit}a:focus-visible,summary:focus-visible{outline:3px solid var(--focus);outline-offset:4px}::selection{background:#dbeafe;color:var(--ink)}
|
||||||
|
.wrap{width:min(100% - 48px,1120px);margin-inline:auto}.site-head{position:sticky;top:0;z-index:10;background:#ffffffed;border-bottom:1px solid var(--line);backdrop-filter:blur(16px)}
|
||||||
|
.site-head .wrap{min-height:74px;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand{display:flex;align-items:center;gap:10px;min-width:0;text-decoration:none}.brand img{width:39px;height:39px;object-fit:contain}.brand strong{display:block;font-size:15px;line-height:1.5}.brand small{display:block;color:var(--muted);font-size:11px;line-height:1.5}
|
||||||
|
.top-nav{display:flex;align-items:center;gap:5px}.top-nav a{padding:9px 15px;border-radius:999px;text-decoration:none;font-size:14px;font-weight:600;color:var(--muted)}.top-nav a:hover,.top-nav a.active{background:var(--wash);color:var(--ink)}
|
||||||
|
.hero{background:linear-gradient(180deg,#f3f8ff 0%,#fff 100%);text-align:center;padding:clamp(78px,9vw,126px) 0 100px}.hero h1{max-width:19ch;margin:0 auto 23px;font-size:clamp(36px,4.4vw,61px);line-height:1.33;font-weight:850;letter-spacing:-.025em}.hero .lead{max-width:61ch;margin:0 auto;color:var(--muted);font-size:clamp(17px,1.6vw,20px);line-height:1.95}.hero .actions{margin-top:34px}
|
||||||
|
.actions{display:flex;flex-wrap:wrap;justify-content:center;gap:12px}.button{display:inline-flex;align-items:center;justify-content:center;min-height:48px;padding:10px 24px;border-radius:10px;border:1px solid var(--ink);text-decoration:none;font-weight:700;line-height:1.5;transition:background .18s,color .18s,border-color .18s}.button.primary{background:var(--ink);color:white}.button.secondary{background:white;color:var(--ink);border-color:#cbd5e1}.button:hover{background:var(--accent);border-color:var(--accent);color:white}
|
||||||
|
.section{padding:86px 0}.section-head{display:flex;justify-content:space-between;align-items:end;gap:24px;margin-bottom:26px}.section h2{font-size:clamp(26px,3vw,35px);line-height:1.45;margin:0}.section-head p{max-width:47ch;margin:0;color:var(--muted)}
|
||||||
|
.offer-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.offer{padding:27px;border:1px solid var(--line);border-radius:16px;background:white}.offer h3{font-size:20px;margin:0 0 10px}.offer p{color:var(--muted);font-size:15px;line-height:1.9;margin:0 0 20px}.text-link{color:var(--accent);text-decoration:underline;text-underline-offset:5px;font-weight:700}.text-link:hover{color:var(--ink)}
|
||||||
|
.home-join{background:var(--wash);border-block:1px solid var(--line);text-align:center}.home-join p{color:var(--muted);max-width:50ch;margin:12px auto 24px}.about-hero{padding:86px 0 70px;background:linear-gradient(180deg,#f4f8fc,white)}.about-hero h1{font-size:clamp(35px,4.3vw,55px);line-height:1.35;margin:0 0 16px}.about-hero p{max-width:68ch;margin:0;color:var(--muted);font-size:18px;line-height:1.95}.about-full-name{margin-top:30px;padding-top:18px;border-top:1px solid var(--line);font-size:14px;color:var(--muted)}
|
||||||
|
.about-layout{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,.8fr);gap:clamp(32px,6vw,90px)}.about-layout h2{margin:0 0 16px}.about-layout p{max-width:66ch;color:var(--muted);margin:0 0 22px}.activity-list{list-style:none;padding:0;margin:0}.activity-list li{padding:15px 0;border-bottom:1px solid var(--line)}.activity-list strong{display:block;font-size:17px}.activity-list span{display:block;color:var(--muted);font-size:14px}.contact-block{border:1px solid var(--line);border-radius:16px;padding:25px;background:var(--wash);align-self:start}.contact-block h3{font-size:20px;margin:0 0 10px}.contact-block p{font-size:14px;margin:0 0 18px}.contact-block a{display:block;overflow-wrap:anywhere;margin:8px 0;color:var(--accent);text-underline-offset:4px}.contact-block a[dir=ltr]{text-align:right}
|
||||||
|
.faq{padding-top:0}.faq details{border-bottom:1px solid var(--line);padding:17px 0}.faq summary{cursor:pointer;font-weight:650}.faq details p{color:var(--muted);margin:12px 0 0;max-width:65ch}.site-foot{border-top:1px solid var(--line);padding:35px 0;color:var(--muted);font-size:14px}.site-foot .wrap{display:flex;justify-content:space-between;gap:16px}.mobile-nav{display:none}
|
||||||
|
@media(max-width:700px){.wrap{width:min(100% - 32px,1120px)}.site-head .wrap{min-height:68px}.brand img{width:33px;height:33px}.brand small{display:none}.top-nav{display:none}.hero{padding:82px 0 72px}.hero h1{font-size:clamp(35px,9vw,48px)}.hero .lead{font-size:17px}.actions{flex-direction:column}.button{width:100%}.section{padding:62px 0}.section-head{display:block}.section-head p{margin-top:8px}.offer-grid,.about-layout{grid-template-columns:1fr}.offer{padding:23px}.about-hero{padding:70px 0 52px}.about-hero p{font-size:17px}.contact-block{margin-top:10px}.faq{padding-top:0}.site-foot{padding-bottom:130px}.site-foot .wrap{display:block}.site-foot p{margin:0 0 8px}.mobile-nav{position:fixed;z-index:20;bottom:calc(12px + env(safe-area-inset-bottom));left:16px;right:16px;display:flex;justify-content:space-around;gap:4px;padding:8px;border-radius:20px;background:#fffffff0;border:1px solid var(--line);box-shadow:0 12px 32px #0f172a20;backdrop-filter:blur(14px)}.mobile-nav a{flex:1;text-align:center;text-decoration:none;padding:7px 2px;border-radius:14px;font-size:12px;font-weight:700}.mobile-nav a.active{background:var(--ink);color:white}}
|
||||||
|
#about,#home,#contact{scroll-margin-top:80px}
|
||||||
|
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}.button{transition:none}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-head"><div class="wrap"><a class="brand" href="#home"><img src="./assets/favicon.ico" alt=""><span><strong>انجمن علمی مهندسی کامپیوتر</strong><small>دانشکدهی فنی و مهندسی شرق گیلان</small></span></a><nav class="top-nav" aria-label="بخشهای نمونه"><a class="active" href="#home">خانه</a><a href="#about">درباره انجمن</a><a href="https://east-guilan-ce.ir/events">رویدادها</a><a href="https://east-guilan-ce.ir/blog">بلاگ</a></nav></div></header>
|
||||||
|
<main>
|
||||||
|
<section class="hero" id="home" aria-labelledby="home-title"><div class="wrap"><h1 id="home-title">انجمن علمی مهندسی کامپیوتر</h1><p class="lead">انجمن دانشجویان کامپیوتر در دانشکدهی فنی و مهندسی شرق گیلان است؛ جایی برای یادگیری، برگزاری رویدادهای تخصصی و همکاری با یکدیگر.</p><div class="actions"><a class="button primary" href="#about">آشنایی با انجمن</a><a class="button secondary" href="https://east-guilan-ce.ir/events">مشاهده رویدادها</a></div></div></section>
|
||||||
|
<section class="section" aria-labelledby="offer-title"><div class="wrap"><div class="section-head"><h2 id="offer-title">در انجمن چه میگذرد؟</h2><p>سه راه ساده برای شناختن فعالیتهای انجمن و پیدا کردن نقطهی شروع.</p></div><div class="offer-grid"><article class="offer"><h3>رویدادهای تخصصی</h3><p>کارگاهها، سمینارها و نشستهایی برای یادگیری مهارتهای تازه.</p><a class="text-link" href="https://east-guilan-ce.ir/events">دیدن رویدادها</a></article><article class="offer"><h3>محتوای آموزشی</h3><p>مطالبی در حوزههای گوناگون علوم و مهندسی کامپیوتر.</p><a class="text-link" href="https://east-guilan-ce.ir/blog">خواندن بلاگ</a></article><article class="offer"><h3>همکاری دانشجویی</h3><p>فرصتی برای مشارکت در فعالیتها و ارتباط با دانشجویان علاقهمند.</p><a class="text-link" href="#contact">راههای ارتباطی</a></article></div></div></section>
|
||||||
|
<section class="section home-join" aria-labelledby="join-title"><div class="wrap"><h2 id="join-title">دوست دارید همراه انجمن باشید؟</h2><p>برای آشنایی با فعالیتها از همینجا شروع کنید یا برنامههای پیش رو را ببینید.</p><div class="actions"><a class="button primary" href="#about">درباره انجمن</a><a class="button secondary" href="https://east-guilan-ce.ir/events">رویدادها</a></div></div></section>
|
||||||
|
<section class="about-hero" id="about" aria-labelledby="about-title"><div class="wrap"><h1 id="about-title">درباره انجمن</h1><p>انجمن علمی مهندسی کامپیوتر در دانشکدهی فنی و مهندسی شرق گیلان با برگزاری رویدادها و تولید محتوای آموزشی، فضایی برای یادگیری و همکاری دانشجویان فراهم میکند.</p><div class="about-full-name">انجمن علمی مهندسی کامپیوتر دانشگاه گیلان (دانشکدهی فنی و مهندسی شرق گیلان)</div></div></section>
|
||||||
|
<section class="section" aria-labelledby="activity-title"><div class="wrap about-layout"><div><h2 id="activity-title">فعالیتهای انجمن</h2><p>محور فعالیتها، یادگیری، اشتراک دانش و ارتباط میان دانشجویان و متخصصان است.</p><ul class="activity-list"><li><strong>برگزاری رویدادها</strong><span>کارگاهها، سمینارها و نشستهای تخصصی</span></li><li><strong>انتشار محتوا</strong><span>مقالهها و مطالب آموزشی در حوزهی کامپیوتر</span></li><li><strong>مشارکت دانشجویی</strong><span>همکاری در برنامهریزی و اجرای فعالیتهای انجمن</span></li></ul></div><aside class="contact-block" id="contact"><h3>ارتباط با انجمن</h3><p>برای پیشنهاد برنامه، همکاری یا پرسش، از یکی از راههای زیر پیام بدهید.</p><a dir="ltr" href="mailto:eastguilanceassociation@gmail.com">eastguilanceassociation@gmail.com</a><a dir="ltr" href="https://t.me/guilancea">t.me/guilancea</a><a dir="ltr" href="https://instagram.com/guilance.ir">instagram.com/guilance.ir</a></aside></div></section>
|
||||||
|
<section class="section faq" aria-labelledby="faq-title"><div class="wrap"><h2 id="faq-title">پرسشهای رایج</h2><details><summary>چطور در رویدادها شرکت کنم؟</summary><p>رویداد موردنظرتان را انتخاب کنید و شرایط و مراحل ثبتنام همان رویداد را بخوانید.</p></details><details><summary>چطور با انجمن همکاری کنم؟</summary><p>برای پیشنهاد همکاری از راههای ارتباطی بالا پیام بدهید.</p></details></div></section>
|
||||||
|
</main>
|
||||||
|
<footer class="site-foot"><div class="wrap"><p>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</p><a href="#home">بازگشت به آغاز</a></div></footer>
|
||||||
|
<nav class="mobile-nav" aria-label="ناوبری نمونه در موبایل"><a class="active" href="#home">خانه</a><a href="#about">درباره</a><a href="https://east-guilan-ce.ir/events">رویدادها</a><a href="https://east-guilan-ce.ir/blog">بلاگ</a></nav>
|
||||||
|
<script>
|
||||||
|
function syncPreviewNav(){
|
||||||
|
const current = location.hash === '#about' ? '#about' : '#home';
|
||||||
|
for(const link of document.querySelectorAll('.top-nav a[href^="#"],.mobile-nav a[href^="#"]')){
|
||||||
|
const active = link.getAttribute('href') === current;
|
||||||
|
link.classList.toggle('active',active);
|
||||||
|
if(active) link.setAttribute('aria-current','page'); else link.removeAttribute('aria-current');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addEventListener('hashchange',syncPreviewNav);
|
||||||
|
syncPreviewNav();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
tasks/evidence/2026-09-24/concept-a-1440.png
Normal file
BIN
tasks/evidence/2026-09-24/concept-a-1440.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 197 KiB |
BIN
tasks/evidence/2026-09-24/concept-a-390.png
Normal file
BIN
tasks/evidence/2026-09-24/concept-a-390.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
BIN
tasks/evidence/2026-09-24/concept-b-1440.png
Normal file
BIN
tasks/evidence/2026-09-24/concept-b-1440.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
BIN
tasks/evidence/2026-09-24/concept-b-390.png
Normal file
BIN
tasks/evidence/2026-09-24/concept-b-390.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
74
tasks/evidence/2026-09-24/create-local-baseline-fixtures.py
Normal file
74
tasks/evidence/2026-09-24/create-local-baseline-fixtures.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"""Create disposable Bundle 0 browser fixtures in the isolated Django test database.
|
||||||
|
|
||||||
|
Run only with --settings=config.settings.test and BUNDLE0_TEST_PASSWORD set.
|
||||||
|
Never point TEST_DB_NAME at a shared or production database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from apps.blog.models import Post
|
||||||
|
from apps.events.models import Event
|
||||||
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
|
||||||
|
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
|
||||||
|
password = os.environ["BUNDLE0_TEST_PASSWORD"]
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
|
||||||
|
def fixture_user(username, mobile, *, staff=False):
|
||||||
|
user, _ = User.objects.get_or_create(
|
||||||
|
username=username,
|
||||||
|
defaults={"mobile": mobile, "email": f"{username}@example.invalid"},
|
||||||
|
)
|
||||||
|
user.mobile = mobile
|
||||||
|
user.email = f"{username}@example.invalid"
|
||||||
|
user.is_email_verified = True
|
||||||
|
user.is_mobile_verified = True
|
||||||
|
user.is_staff = staff
|
||||||
|
user.is_superuser = staff
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
student = fixture_user("bundle0_student", "09198000111")
|
||||||
|
staff = fixture_user("bundle0_staff", "09198000112", staff=True)
|
||||||
|
|
||||||
|
for slug, title, price, capacity in (
|
||||||
|
("bundle0-free", "Bundle 0 Free Event", 0, 20),
|
||||||
|
("bundle0-full", "Bundle 0 Full Event", 0, 0),
|
||||||
|
("bundle0-paid", "Bundle 0 Paid Event", 50000, 20),
|
||||||
|
):
|
||||||
|
Event.objects.update_or_create(
|
||||||
|
slug=slug,
|
||||||
|
defaults={
|
||||||
|
"title": title,
|
||||||
|
"description": "Local baseline event for browser review.",
|
||||||
|
"start_time": now + timedelta(days=7),
|
||||||
|
"end_time": now + timedelta(days=7, hours=2),
|
||||||
|
"registration_start_date": now - timedelta(days=1),
|
||||||
|
"registration_end_date": now + timedelta(days=6),
|
||||||
|
"status": Event.StatusChoices.PUBLISHED,
|
||||||
|
"price": price,
|
||||||
|
"capacity": capacity,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Post.objects.update_or_create(
|
||||||
|
slug="bundle0-article",
|
||||||
|
defaults={
|
||||||
|
"title": "Bundle 0 Article",
|
||||||
|
"content": "# Local baseline article\n\nA sample article for the browser review.",
|
||||||
|
"author": staff,
|
||||||
|
"status": Post.StatusChoices.PUBLISHED,
|
||||||
|
"published_at": now,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Created isolated student, staff, free/full/paid events, and article fixtures.")
|
||||||
95
tasks/evidence/2026-09-24/replay-local-auth.py
Normal file
95
tasks/evidence/2026-09-24/replay-local-auth.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
"""Replay signup, OTP login, and password reset in the isolated test database.
|
||||||
|
|
||||||
|
SMS and the short-lived OTP store are mocked. Run after the fixture seed with
|
||||||
|
--settings=config.settings.test and BUNDLE0_TEST_PASSWORD set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test import Client, override_settings
|
||||||
|
|
||||||
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
|
||||||
|
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
|
||||||
|
password = os.environ["BUNDLE0_TEST_PASSWORD"]
|
||||||
|
mobile = "09198000113"
|
||||||
|
client = Client()
|
||||||
|
|
||||||
|
|
||||||
|
class TemporaryOtpStore:
|
||||||
|
def __init__(self):
|
||||||
|
self.values = {}
|
||||||
|
|
||||||
|
def setex(self, key, _seconds, value):
|
||||||
|
self.values[key] = str(value).encode("utf-8")
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
return self.values.get(key)
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
self.values.pop(key, None)
|
||||||
|
|
||||||
|
|
||||||
|
store = TemporaryOtpStore()
|
||||||
|
|
||||||
|
|
||||||
|
def post(path, payload):
|
||||||
|
return client.post(path, data=json.dumps(payload), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
with (
|
||||||
|
override_settings(ALLOWED_HOSTS=["testserver", "localhost"]),
|
||||||
|
patch("apps.users.services.auth._redis", return_value=store),
|
||||||
|
patch("apps.users.services.auth.send_critical_sms.delay"),
|
||||||
|
):
|
||||||
|
invalid = post("/api/auth/otp/send", {"mobile": "123", "mode": "register"})
|
||||||
|
assert invalid.status_code == 400
|
||||||
|
|
||||||
|
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "register"})
|
||||||
|
assert sent.status_code == 200, sent.content
|
||||||
|
code = store.get(f"auth_otp:register:{mobile}").decode("utf-8")
|
||||||
|
wrong = "00000" if code != "00000" else "99999"
|
||||||
|
rejected = post("/api/auth/otp/verify-register", {"mobile": mobile, "code": wrong})
|
||||||
|
assert rejected.status_code == 400
|
||||||
|
verified = post("/api/auth/otp/verify-register", {"mobile": mobile, "code": code})
|
||||||
|
assert verified.status_code == 200, verified.content
|
||||||
|
registered = post(
|
||||||
|
"/api/auth/register",
|
||||||
|
{"username": "bundle0_signup", "mobile": mobile, "code": code, "password": password},
|
||||||
|
)
|
||||||
|
assert registered.status_code == 201, registered.content
|
||||||
|
assert User.objects.get(username="bundle0_signup").is_mobile_verified
|
||||||
|
|
||||||
|
duplicate = post("/api/auth/otp/send", {"mobile": mobile, "mode": "register"})
|
||||||
|
assert duplicate.status_code == 400
|
||||||
|
|
||||||
|
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "login"})
|
||||||
|
assert sent.status_code == 200, sent.content
|
||||||
|
code = store.get(f"auth_otp:login:{mobile}").decode("utf-8")
|
||||||
|
login = post("/api/auth/login/otp", {"mobile": mobile, "code": code})
|
||||||
|
assert login.status_code == 200 and login.json()["access_token"]
|
||||||
|
|
||||||
|
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "reset_password"})
|
||||||
|
assert sent.status_code == 200, sent.content
|
||||||
|
code = store.get(f"auth_otp:reset_password:{mobile}").decode("utf-8")
|
||||||
|
new_password = password + "New"
|
||||||
|
rejected = post(
|
||||||
|
"/api/auth/reset-password",
|
||||||
|
{"mobile": mobile, "code": wrong if wrong != code else "88888", "new_password": new_password},
|
||||||
|
)
|
||||||
|
assert rejected.status_code == 400
|
||||||
|
changed = post(
|
||||||
|
"/api/auth/reset-password",
|
||||||
|
{"mobile": mobile, "code": code, "new_password": new_password},
|
||||||
|
)
|
||||||
|
assert changed.status_code == 200, changed.content
|
||||||
|
old_login = post("/api/auth/login", {"identifier": mobile, "password": password})
|
||||||
|
new_login = post("/api/auth/login", {"identifier": mobile, "password": new_password})
|
||||||
|
assert old_login.status_code == 401 and new_login.status_code == 200
|
||||||
|
|
||||||
|
print("Auth replay: invalid OTP request and code rejected; signup, OTP login, and password reset succeeded; old password rejected.")
|
||||||
85
tasks/evidence/2026-09-24/replay-local-payment.py
Normal file
85
tasks/evidence/2026-09-24/replay-local-payment.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"""Replay paid-event outcomes in the isolated Bundle 0 Django test database.
|
||||||
|
|
||||||
|
External gateway calls, notifications, and SMS are mocked. This script is baseline
|
||||||
|
evidence, not a production payment test or a browser gateway substitute.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test import Client, override_settings
|
||||||
|
|
||||||
|
from apps.events.models import Event, Registration
|
||||||
|
from apps.payments.models import Payment
|
||||||
|
from apps.users.models import User
|
||||||
|
from core.authentication import create_jwt_token
|
||||||
|
|
||||||
|
|
||||||
|
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
|
||||||
|
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
|
||||||
|
|
||||||
|
student = User.objects.get(username="bundle0_student")
|
||||||
|
event = Event.objects.get(slug="bundle0-paid")
|
||||||
|
client = Client()
|
||||||
|
headers = {"HTTP_AUTHORIZATION": f"Bearer {create_jwt_token(student)}"}
|
||||||
|
body = json.dumps({"event_id": event.pk, "description": "Local mocked payment replay"})
|
||||||
|
|
||||||
|
|
||||||
|
def create_payment():
|
||||||
|
return client.post(
|
||||||
|
"/api/payments/create",
|
||||||
|
data=body,
|
||||||
|
content_type="application/json",
|
||||||
|
**headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
with (
|
||||||
|
override_settings(ALLOWED_HOSTS=["testserver", "localhost"]),
|
||||||
|
patch("apps.payments.api.views.notify_user"),
|
||||||
|
patch("apps.payments.api.views.send_critical_sms.delay"),
|
||||||
|
patch("apps.payments.api.views.requests.post") as gateway,
|
||||||
|
):
|
||||||
|
gateway.side_effect = RuntimeError("simulated gateway outage")
|
||||||
|
failed_request = create_payment()
|
||||||
|
assert failed_request.status_code == 502, failed_request.content
|
||||||
|
assert not Payment.objects.filter(user=student, event=event).exists()
|
||||||
|
|
||||||
|
gateway.side_effect = None
|
||||||
|
gateway.return_value = Mock()
|
||||||
|
gateway.return_value.json.return_value = {
|
||||||
|
"data": {"code": 100, "authority": "BUNDLE0-CANCEL"}
|
||||||
|
}
|
||||||
|
pending = create_payment()
|
||||||
|
assert pending.status_code == 200, pending.content
|
||||||
|
assert pending.json()["start_pay_url"]
|
||||||
|
canceled = client.get(
|
||||||
|
"/api/payments/callback",
|
||||||
|
{"Authority": "BUNDLE0-CANCEL", "Status": "NOK"},
|
||||||
|
)
|
||||||
|
assert canceled.status_code == 302
|
||||||
|
assert "status=failed" in canceled.url
|
||||||
|
assert Payment.objects.get(authority="BUNDLE0-CANCEL").status == Payment.OrderStatusChoices.CANCELED
|
||||||
|
|
||||||
|
gateway.return_value.json.return_value = {
|
||||||
|
"data": {"code": 100, "authority": "BUNDLE0-SUCCESS"}
|
||||||
|
}
|
||||||
|
pending = create_payment()
|
||||||
|
assert pending.status_code == 200, pending.content
|
||||||
|
gateway.return_value.json.return_value = {
|
||||||
|
"data": {"code": 100, "ref_id": "BUNDLE0-REF"}
|
||||||
|
}
|
||||||
|
paid = client.get(
|
||||||
|
"/api/payments/callback",
|
||||||
|
{"Authority": "BUNDLE0-SUCCESS", "Status": "OK"},
|
||||||
|
)
|
||||||
|
assert paid.status_code == 302
|
||||||
|
assert "status=success" in paid.url
|
||||||
|
assert Payment.objects.get(authority="BUNDLE0-SUCCESS").status == Payment.OrderStatusChoices.PAID
|
||||||
|
assert Registration.objects.get(user=student, event=event).status == Registration.StatusChoices.CONFIRMED
|
||||||
|
duplicate = create_payment()
|
||||||
|
assert duplicate.status_code == 400
|
||||||
|
|
||||||
|
print("Paid replay: gateway outage 502; cancellation redirect; mocked success and confirmed registration; duplicate blocked.")
|
||||||
@@ -33,10 +33,10 @@ Checkpoint C was approved by the owner on 2026-09-27 after reviewing [the schema
|
|||||||
|
|
||||||
## 2. Home/About and shared public shell (9–12 person-days)
|
## 2. Home/About and shared public shell (9–12 person-days)
|
||||||
|
|
||||||
- [ ] **T15 — Establish approved visual tokens and shell (M; deps: T05, T14).** Files: F global styles, fonts, navigation/footer components. Acceptance: approved typography/colors/layout, responsive header/footer, and CMS identity/navigation/contact fields match comps; RTL/keyboard/reduced-motion work. Verify: screenshot comparison at 320/390/768/1440px and keyboard pass.
|
- [x] **T15 — Establish approved visual tokens and shell (M; deps: T05, T14).** Files: F global styles, fonts, navigation/footer components. Acceptance: approved typography/colors/layout, responsive header/footer, and CMS identity/navigation/contact fields match comps; RTL/keyboard/reduced-motion work. Implementation reviewed and approved by the owner at Checkpoint G on 2026-09-28.
|
||||||
- [ ] **T16 — Build CMS-driven Home editorial sections and domain previews (M; deps: T15).** Files: F Home view, section components, tests. Acceptance: hero/featured-section/CTA text comes from the current CMS record in the fixed owner-approved order; event and blog cards read existing domain APIs with truthful status, useful links, and sound empty/long layouts. Verify: an admin Save changes the public text on the next request; event/blog source changes update cards; visual review at all widths.
|
- [x] **T16 — Build CMS-driven Home editorial sections and domain previews (M; deps: T15).** Files: F Home view, section components, tests. Acceptance: hero/featured-section/CTA text comes from the current CMS record in the fixed owner-approved order; event and blog cards read existing domain APIs with truthful status, useful links, and sound empty/long layouts. Home was reviewed and approved by the owner at Checkpoint G on 2026-09-28; frontend lint and production build passed.
|
||||||
- [ ] **Checkpoint G (deps: T15–T16).** Home concept fidelity and real content signed off before About.
|
- [x] **Checkpoint G (deps: T15–T16).** Owner approved the Home design and content at target widths on 2026-09-28. T17 can proceed.
|
||||||
- [ ] **T17 — Build CMS-driven About sections (M; deps: T15).** Files: F About view, section components, tests. Acceptance: official identity, mission, committee/association facts, and contacts come from reviewed current CMS records; unsupported claims are absent. Verify: compare rendered content to approved starting values and confirm an admin Save changes the page.
|
- [ ] **T17 — Build CMS-driven About sections (M; deps: T15).** Files: F About view, section components, tests. Acceptance: owner-approved About identity, purpose, member-year archive, selected event highlights, separate memories gallery, and general history timeline come from current CMS records; unsupported claims are absent. Implementation is ready in the frontend working tree; final content and admin-save review remain open. Verify: compare rendered content to approved starting values and confirm an admin Save changes the page.
|
||||||
- [ ] **T18 — Make page SEO/structured data content-driven (M; deps: T16–T17).** Files: F route metadata/SEO utilities/tests. Acceptance: canonical, title, description, OG image, and structured-data identity are consistent with current CMS records and server-rendered HTML; no stale hardcoded names. Verify: inspect HTML/JSON-LD and link previews on staging after an admin Save.
|
- [ ] **T18 — Make page SEO/structured data content-driven (M; deps: T16–T17).** Files: F route metadata/SEO utilities/tests. Acceptance: canonical, title, description, OG image, and structured-data identity are consistent with current CMS records and server-rendered HTML; no stale hardcoded names. Verify: inspect HTML/JSON-LD and link previews on staging after an admin Save.
|
||||||
- [ ] **T19 — Finish Home/About interaction and accessibility pass (M; deps: T16–T18).** Files: F affected page/styles/tests. Acceptance: motion supports comprehension but does not block content, reduced-motion works, contrast/focus/read order and Persian line wraps pass. Verify: real browser desktop/mobile captures, keyboard, screen-reader spot check, and Impeccable critique against approved comp.
|
- [ ] **T19 — Finish Home/About interaction and accessibility pass (M; deps: T16–T18).** Files: F affected page/styles/tests. Acceptance: motion supports comprehension but does not block content, reduced-motion works, contrast/focus/read order and Persian line wraps pass. Verify: real browser desktop/mobile captures, keyboard, screen-reader spot check, and Impeccable critique against approved comp.
|
||||||
- [ ] **Checkpoint H (deps: T17–T19).** Owner approves Home/About on staging; authorized staff can Save a visible change without developer help.
|
- [ ] **Checkpoint H (deps: T17–T19).** Owner approves Home/About on staging; authorized staff can Save a visible change without developer help.
|
||||||
|
|||||||
Reference in New Issue
Block a user